問題現場
- 服務端配置
character-set-server = utf8
- 客戶端指定
--default-character-set=utf8 或 SET NAMES latin1
character_set_client/connection/results **始終跟隨服務端**,客戶端設置無效
- 唯有連接後手動
SET NAMES latin1 才能臨時改變
根因:skip-character-set-client-handshake
[mysqld]
character-set-server = utf8
skip-character-set-client-handshake # 罪魁禍首
官方文檔解讀
--character-set-client-handshake:不忽略客戶端發送的字符集信息(默認)。
--skip-character-set-client-handshake:忽略客戶端字符集信息,**強制使用服務端默認字符集**,行為類似 MySQL 4.0。
機制對比
| 場景 | 默認 (handshake) | skip-handshake |
| 握手階段 | 客戶端發送字符集 → 服務端確認/協商 | 服務端無視客戶端,直接用 character-set-server |
| SET NAMES | 動態修改會話字符集 | 可用,但僅影響當前會話 |
| --default-character-set | 生效 | **無效** |
優缺點權衡
優點:防止亂碼源頭
- 杜絕應用層誤用 latin1/gbk 連接寫入 utf8 表導致亂碼
- 統一字符集策略,降低運維認知負擔
- 兼容老版本驅動(無字符集協商能力)
缺點:靈活性丟失
- 無法按會話/應用使用不同字符集
- 遷移字符集時(如 utf8 → utf8mb4)需協調所有客戶端
- 調試字符集問題時難以復現客戶端視角
最佳實踐建議
- ✅ **新架構建議**:不開啟 skip-handshake,在應用層/連接池統一設置字符集(如 HikariCP
connectionInitSql=SET NAMES utf8mb4)
- ✅ **存量系統**:若已依賴此參數穩定運行,**勿輕易關閉**,做好變更測試
- ✅ **字符集遷移期**:臨時關閉 skip-handshake,雙寫驗證,再統一遷移
- ✅ **監控指標**:收集
character_set_client 分佈,異常值報警
驗證腳本
-- 連接後立即執行
SHOW VARIABLES LIKE 'character_set_%';
SHOW VARIABLES LIKE 'collation_%';
-- 重點觀察 client/connection/results 是否一致