latin1,實際存 GBK/GB2312MySQL **不驗證** 寫入內容是否符合聲稱字符集。
latin1= 單字節 → 實際存雙字節 GBK → 導出時若按 latin1 讀取再按 utf8 寫入 = 雙重錯誤編碼
SHOW CREATE TABLE t\G
-- 查看 DEFAULT CHARSET=latin1
SELECT col, HEX(col) FROM t WHERE col LIKE '%中文%';
-- HEX 應呈現 GBK 編碼 (如 D6D0 CEC4 = "中文" 的 GBK)
# 目標庫建為 utf8mb4
CREATE DATABASE new_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
# 導出結構 (去除 latin1)
mysqldump -u root -p --no-data --skip-set-charset old_db |
sed 's/CHARSET=latin1/CHARSET=utf8mb4/g' > struct.sql
mysql -u root -p new_db < struct.sql
# 導出數據 (按 latin1 讀取 = 原始位元組)
mysqldump -u root -p --default-character-set=latin1 --no-create-info --skip-set-charset old_db > data.sql
# 導入數據 (按 GBK 解讀 → 存 utf8mb4)
mysql -u root -p --default-character-set=gbk new_db < data.sql
[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
init_connect = 'SET NAMES utf8mb4'
skip-character-set-client-handshake # 強制統一,忽略客戶端請求
[client]
default-character-set = utf8mb4
[mysql]
default-character-set = utf8mb4
修改後必須重啟 MySQL,存儲過程需重建 (見文章 89251)。
| 誤操作 | 後果 | 正確做法 |
|---|---|---|
直接 ALTER TABLE t CONVERT TO CHARACTER SET utf8mb4 | MySQL 認為數據是 latin1,執行 latin1→utf8 轉換,GBK 位元組被錯誤轉碼 → 永久損壞 | 先 ALTER TABLE t CHARSET=utf8mb4 (只改元數據),再按上述流程重導數據 |
導出時加 --default-character-set=utf8 | 強制 latin1→utf8 轉換,同樣損壞 | 導出用 --default-character-set=latin1 (或不指定,視版本) |
導入時不加 --default-character-set=gbk | MySQL 按連線字符集 (通常 utf8) 解讀,產生亂碼 | 導入必須顯式指定源數據實際編碼 (GBK/GB2312) |
忽略 SET NAMES latin1 干擾 | 導入時被覆蓋字符集聲明 | 導出加 --skip-set-charset 或事後 sed '/SET NAMES/d' |
SELECT
col,
HEX(col) AS hex_bytes,
CHAR_LENGTH(col) AS char_len,
LENGTH(col) AS byte_len,
col = CONVERT(CONVERT(col USING latin1) USING gbk) AS is_gbk_in_latin1
FROM new_db.target_table
WHERE col LIKE '%中文%'
LIMIT 10;
-- 正常 utf8mb4: hex_bytes 如 E4B8ADE69687, char_len=2, byte_len=6, is_gbk_in_latin1=0
MySQL 亂碼本質 = 聲稱編碼 ≠ 實際編碼。遷移鐵律:**導出按聲稱編碼 (latin1) 原樣讀出 → 導入按實際編碼 (GBK) 解讀 → 存儲為目標編碼 (utf8mb4)**。配置文件統一 utf8mb4 + 重啟 + 存儲過程重建,徹底斷根。