MySQL 存儲過程中頻發兩類中文問題:
WHERE name = param 條件查詢,明明有數據卻返回空結果。根因統一指向:**字符集鏈路不一致**,特別是**連接層字符集 (character_set_connection) 與數據存儲字符集脫節**。
| 層級 | 系統變量 | 作用 | 典型值 |
|---|---|---|---|
| 服務器級 | character_set_server | 默認庫/表字符集 | utf8mb4 |
| 數據庫級 | character_set_database | 當前庫默認字符集 | utf8mb4 |
| 表級 | character_set_table (隱含) | 表默認字符集 (建表時指定) | utf8mb4 |
| 列級 | 列定義 CHARSET | 具體列存儲編碼 | utf8mb4 |
| 連接級 (關鍵) | character_set_client | 客戶端發送 SQL 的編碼 | utf8mb4 |
character_set_connection | Server 解析 SQL 時將客戶端數據轉換為的內部編碼 | utf8mb4 | |
character_set_results | Server 返回結果集給客戶端的編碼 | utf8mb4 | |
collation_connection | 連接層排序規則,影響比較、排序、分組 | utf8mb4_unicode_ci |
關鍵認知:存儲過程在 Server 端運行,但其參數傳遞、SQL 構建、結果返回都經過**連接層字符集轉換**。若連接層為 latin1 而數據為 utf8mb4,中文會在「客戶端→連接層→存儲引擎」兩次轉換中損壞。
SET NAMES (最常用、最有效)-- 在調用存儲過程的客戶端代碼中 (PHP/Java/Python/C++/Shell)
-- 連接成功後、調用存儲過程前,執行一次:
SET NAMES 'utf8mb4';
-- 等價於:
SET character_set_client = utf8mb4;
SET character_set_connection = utf8mb4;
SET character_set_results = utf8mb4;
各語言具體實現:
charset=utf8mb4$mysqli->set_charset('utf8mb4')?characterEncoding=utf8mb4 (需驅動 5.1.47+)charset='utf8mb4' 參數charset=utf8mb4mysql --default-character-set=utf8mb4[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
init_connect = 'SET NAMES utf8mb4' -- 非 SUPER 權限用戶連接自動執行
skip-character-set-client-handshake = ON -- 忽略客戶端聲明字符集,強制使用服務端設置
重啟 MySQL 後,所有新連接自動使用 utf8mb4,無需客戶端代碼修改。
CREATE PROCEDURE `sp_insert_user`(IN p_name VARCHAR(100))
BEGIN
-- 強制設置當前會話字符集 (僅對當前會話有效)
SET NAMES 'utf8mb4';
INSERT INTO users(name) VALUES (p_name);
END
缺陷:若調用者連接層已錯誤,參數 p_name 在進入存儲過程前已損壞,內部 SET NAMES 無法挽回。
-- 表結構
CREATE TABLE t (name VARCHAR(50) CHARSET utf8mb4) CHARSET=utf8mb4;
INSERT INTO t VALUES ('张三');
-- 存儲過程
CREATE PROCEDURE sp_find(IN p_name VARCHAR(50))
BEGIN
SELECT * FROM t WHERE name = p_name; -- 傳入 '张三' 卻查無結果
END
name 排序規則:utf8mb4_unicode_ci (或表默認)p_name / 字符串常量 排序規則:collation_connection (連接層),若連接層為 latin1_swedish_ci 或 utf8mb4_general_ci,與列排序規則不同,MySQL **無法使用索引**,且可能導致比較語義差異 (如大小寫、重音、全半角敏感度)。-- 客戶端連接後執行
SET NAMES 'utf8mb4' COLLATE 'utf8mb4_unicode_ci';
-- 或 my.cnf
init_connect = 'SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci'
COLLATE (針對性修復)-- 修正存儲過程
CREATE PROCEDURE sp_find(IN p_name VARCHAR(50))
BEGIN
-- 方法 A: 參數側指定
SELECT * FROM t WHERE name = p_name COLLATE utf8mb4_unicode_ci;
-- 方法 B: 列側指定 (同等效果)
SELECT * FROM t WHERE name COLLATE utf8mb4_unicode_ci = p_name;
-- 方法 C: 兩側都指定 (最保險)
SELECT * FROM t WHERE name COLLATE utf8mb4_unicode_ci = p_name COLLATE utf8mb4_unicode_ci;
END
原理:COLLATE 子句強制指定比較操作使用的排序規則,消除隱式推導歧義,且**不影響索引使用** (前提是列索引排序規則與指定一致)。
-- 修改表默認排序規則 (僅影響後續新增列)
ALTER TABLE t DEFAULT COLLATE utf8mb4_unicode_ci;
-- 修改現有列排序規則 (需重建表/列,會鎖表)
ALTER TABLE t MODIFY name VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 或
ALTER TABLE t CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 全表轉換
| 檢查項 | 正確配置 | 檢查命令 |
|---|---|---|
| 服務器默認字符集 | utf8mb4 | SHOW VARIABLES LIKE 'character_set_server'; |
| 服務器默認排序規則 | utf8mb4_unicode_ci | SHOW VARIABLES LIKE 'collation_server'; |
| 目標表字符集 | utf8mb4 | SHOW CREATE TABLE t; |
| 目標列字符集/排序規則 | utf8mb4 / utf8mb4_unicode_ci | SHOW FULL COLUMNS FROM t; |
| 當前會話連接字符集 | utf8mb4 | SHOW VARIABLES LIKE 'character_set_connection'; |
| 當前會話連接排序規則 | utf8mb4_unicode_ci | SHOW VARIABLES LIKE 'collation_connection'; |
| 客戶端驅動配置 | charset=utf8mb4 | 檢查應用代碼 DSN/連接參數 |
| SQL 文件編碼 | UTF-8 無 BOM | file -i script.sql |
my.cnf 強制 character-set-server=utf8mb4 + collation-server=utf8mb4_unicode_ci + init_connect='SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci' + skip-character-set-client-handshake=ON。CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci,遷移腳本包含 CONVERT TO CHARACTER SET。charset=utf8mb4,不依賴服務端默認。COLLATE utf8mb4_unicode_ci。information_schema.COLUMNS 發現非 utf8mb4 列,告警修復。MySQL 存儲過程中文問題 90% 源於**連接層字符集/排序規則與存儲層不一致**。徹底解決之道是:**基礎設施層強制統一 utf8mb4 + utf8mb4_unicode_ci,消除隱式轉換**。存儲過程內部僅作為業務邏輯容器,不應承擔字符集糾錯職責。遵循「配置下沉、顯式優於隱式、全鏈路一致」原則,即可從根源消滅中文亂碼與比較失效顽疾。