「任務是把 latin1 轉為 utf8」—— 錯!
真相:舊庫 (MySQL 4.x/5.0) 宣稱 latin1,實際存 GBK/GB2312 雙字節中文。MySQL 不驗證寫入內容是否符合聲稱字符集。
CREATE DATABASE new_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE new_db;
-- 建表語句同樣指定 CHARSET=utf8mb4
mysqldump -u root -p \
--default-character-set=latin1 \ -- 關鍵:讀取時不轉碼
--no-create-info \ -- 僅導數據,結構單獨處理
--skip-set-charset \ -- 避免產生 SET NAMES latin1 干擾導入
old_db > data_latin1.sql
mysql -u root -p \
--default-character-set=gbk \ -- 關鍵:告知 MySQL 導入數據是 GBK
new_db < data_latin1.sql
# 1. 導出結構 (去除 CHARSET=latin1)
mysqldump -u root -p --no-data --skip-set-charset old_db |
sed 's/CHARSET=latin1/CHARSET=utf8mb4/g' > struct_utf8mb4.sql
# 2. 建立目標庫並導入結構
mysql -u root -p -e "CREATE DATABASE new_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
mysql -u root -p new_db < struct_utf8mb4.sql
# 3. 導出數據 (純 latin1 二進位)
mysqldump -u root -p --default-character-set=latin1 --no-create-info --skip-set-charset old_db > data_latin1.sql
# 4. 導入數據 (按 GBK 解讀 → 存 utf8mb4)
mysql -u root -p --default-character-set=gbk new_db < data_latin1.sql
| 參數 | 作用 | 為什麼需要 |
|---|---|---|
--default-character-set=latin1 (導出) | 讀取時不轉碼,保持原始位元組 | 源表聲稱 latin1,實存 GBK,不轉碼才能保留原始 GBK 位元組 |
--skip-set-charset | 不在輸出中寫入 SET NAMES latin1 | 避免導入時被此語句覆蓋 --default-character-set=gbk |
--default-character-set=gbk (導入) | 告知 MySQL 客戶端發送的數據是 GBK | MySQL 負責 GBK → utf8mb4 轉換並存儲 |
--set-charset=false (8.0+ 別名) | 同 --skip-set-charset | 同上 |
# 假設 SET NAMES 在第 10 行,總 1987 行
head -9 dump.sql > clean.sql
tail -1977 dump.sql >> clean.sql
# 或用 sed 直接刪除
sed -i '/SET NAMES/d' dump.sql
SELECT col, HEX(col), CHAR_LENGTH(col), LENGTH(col)
FROM new_db.target_table
WHERE col LIKE '%中文%';
-- HEX 應顯示 UTF-8 編碼 (如 E4B8ADE69687)
-- CHAR_LENGTH = 字符數, LENGTH = 字節數 (UTF-8 中文 3 字節)
MySQL 遷移亂碼核心在於識別「聲稱字符集」與「實際存儲字符集」的不一致。latin1 導出 + GBK 導入 + utf8mb4 存儲 是經典 latin1→utf8 遷移的黃金法則。MySQL 8.0 預設 utf8mb4,新專案直接用 utf8mb4 避免此類坑。