字符編碼亂碼是 MySQL 開發運維的頭號痛點。本文系統梳理 character_set 六大層級配置、utf8 vs utf8mb4 本質差異、遷移實戰步驟、驗證腳本與常見坑點,構建「零亂碼」交付標準。
┌─────────────────────────────────────┐ │ 1. Server (character_set_server) │ ← my.cnf [mysqld] 全域預設 ├─────────────────────────────────────┤ │ 2. Database (DEFAULT CHARACTER SET) │ ← CREATE/ALTER DATABASE 繼承 Server ├─────────────────────────────────────┤ │ 3. Table (DEFAULT CHARSET) │ ← CREATE/ALTER TABLE 繼承 Database ├─────────────────────────────────────┤ │ 4. Column (CHARACTER SET) │ ← CREATE/ALTER TABLE ... COLUMN 繼承 Table ├─────────────────────────────────────┤ │ 5. Connection (character_set_*) │ ← 客戶端連線協議,三要素:client/connection/results ├─────────────────────────────────────┤ │ 6. Literal/Constant (_charset'str') │ ← SQL 字面值強制編碼,最高優先級 └─────────────────────────────────────┘
character_set_client:客戶端發送 SQL 的編碼character_set_connection:Server 解析 SQL 時的編碼character_set_results:Server 返回結果集的編碼最佳實踐: 三者統一為 utf8mb4,避免隱式轉換資訊損失。
| 特性 | utf8 (utf8mb3) | utf8mb4 |
|---|---|---|
| 最大位元組/字元 | 3 Bytes | 4 Bytes |
| Unicode 覆蓋 | BMP 平面 (U+0000-U+FFFF) | 完整 Unicode (含補充平面 U+10000-U+10FFFF) |
| Emoji 支援 | ❌ 儲存報錯/截斷 | ✅ 完美支援 |
| 生僻字支援 | ❌ 部分字符無法儲存 | ✅ 全支援 |
| 索引鍵長度限制 | 767 Bytes (5.6-) / 3072 Bytes (5.7+) | 同 utf8,但 4 Bytes/字元實際容納字元數減少 |
| 儲存空間 | 較小 | 非 BMP 字元 +1 Byte |
[mysqld] character-set-server = utf8mb4 collation-server = utf8mb4_unicode_ci # 可選:強制客戶端協議 [client] default-character-set = utf8mb4 [mysql] default-character-set = utf8mb4 [mysqldump] default-character-set = utf8mb4
生效: 重啟服務。影響後續新建庫/表/欄位預設值。
-- 建庫指定 CREATE DATABASE app_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 修改現有庫 (僅影響後續新建表) ALTER DATABASE app_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 建表指定 CREATE TABLE users ( id BIGINT PRIMARY KEY, name VARCHAR(50) ) DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- 修改現有表 (僅影響後續新增欄位) ALTER TABLE users DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 修改欄位編碼 (會重建表,大表謹慎操作)
ALTER TABLE users MODIFY name VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 批次修改表所有字串欄位 (資訊庫生成)
SELECT CONCAT('ALTER TABLE `', TABLE_NAME, '` MODIFY `', COLUMN_NAME, '` ', COLUMN_TYPE, ' CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;')
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'app_db' AND CHARACTER_SET_NAME = 'utf8';
pt-online-schema-change \ --alter "CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci" \ --execute \ --max-load "Threads_running=50" \ --critical-load "Threads_running=100" \ --chunk-time 0.5 \ --chunk-size 1000 \ D=app_db,t=users
name_utf8mb4 欄位,應用雙寫 (舊欄位 + 新欄位)UPDATE ... SET new_col = CONVERT(old_col USING utf8mb4))ALTER TABLE users CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- CONVERT TO 會同時修改表預設、所有字串欄位編碼、資料重新編碼 -- 僅修改預設用 ALTER TABLE ... DEFAULT CHARACTER SET ... (不轉換既有資料)
#!/bin/bash
# verify_charset.sh
mysql -u root -p -e "
SELECT
@@character_set_server AS server_charset,
@@collation_server AS server_collation;
SELECT
TABLE_SCHEMA, TABLE_NAME, TABLE_COLLATION
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA NOT IN ('mysql','information_schema','performance_schema','sys')
AND TABLE_COLLATION NOT LIKE 'utf8mb4%';
SELECT
TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, CHARACTER_SET_NAME, COLLATION_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA NOT IN ('mysql','information_schema','performance_schema','sys')
AND CHARACTER_SET_NAME != 'utf8mb4'
AND DATA_TYPE IN ('char','varchar','text','longtext','mediumtext','tinytext','enum','set');
"
# 實測寫入 Emoji/生僻字
mysql -u root -p -e "
USE app_db;
CREATE TABLE IF NOT EXISTS charset_test (c VARCHAR(20)) CHARSET=utf8mb4;
INSERT INTO charset_test VALUES ('中文測試😀🚀'), ('生僻字:𠮷𠮷𠮷');
SELECT * FROM charset_test;
SELECT HEX(c), CHAR_LENGTH(c), LENGTH(c) FROM charset_test;
"
| 坑點 | 現象 | 根因 | 解法 |
|---|---|---|---|
| 只改 Server 不改既有庫/表/欄位 | 新庫正常,舊庫仍亂碼 | 編碼繼承僅向下,不向上回溯 | 逐層遷移:Server → Database → Table → Column |
| 用 ALTER TABLE ... DEFAULT CHARSET | 既有欄位編碼未變,新欄位正確 | DEFAULT 只影響後續新增欄位 | 必須用 CONVERT TO 或逐欄位 MODIFY |
| 連線三要素未統一 | 寫入正常,讀出亂碼 / 反之 | 隱式轉換截斷/錯位 | SET NAMES utf8mb4 / 連線池配置 characterEncoding=utf8mb4 |
| JDBC URL 缺 characterEncoding | Java 應用亂碼 | 驅動預設 latin1/utf8 | jdbc:mysql://host:3306/db?characterEncoding=utf8mb4&useSSL=false |
| 索引鍵長度超限 (767 Bytes) | 遷移後建索引失敗 | utf8mb4 4 Bytes/字元,VARCHAR(255) 超限 | 5.7+ 自動 3072 Bytes;5.6- 縮短欄位長度或啟用 innodb_large_prefix |
| 排序規則 | 特性 | 推薦場景 |
|---|---|---|
| utf8mb4_unicode_ci | UCA 標準,多語言正確排序,效能略低 | 通用業務、多語系、嚴格正確性要求 |
| utf8mb4_general_ci | 簡化規則,速度快,部分語言排序不準 | 極高效能、單語系、可容忍排序偏差 |
| utf8mb4_0900_ai_ci (8.0+) | Unicode 9.0,效能優於 unicode_ci,支援音標/重音不敏感 | MySQL 8.0+ 首選,現代標準 |
從 latin1 到 utf8 再到 utf8mb4,每一次編碼升級都是為了擁抱更豐富的文字世界。建立「零亂碼」標準,讓每一個中文、Emoji、生僻字都能在資料庫中安穩棲息。