直接alter table ... convert to character set utf8mb4会导致乱码,因为mysql按错误声明的字符集(如latin1)解读utf-8字节,造成二次转码;安全做法是三步法:先转blob跳过解码,再转utf8mb4声明编码,最后校验hex值确保字节不变。

不能直接 ALTER TABLE ... CONVERT TO CHARACTER SET utf8mb4,否则中文、emoji、德语变音符会变成 ?——这不是迁移,是数据破坏。
为什么直接 CONVERT 会导致乱码
MySQL 执行 CONVERT TO CHARACTER SET 时,会按列当前声明的字符集(比如 latin1)去“解读”磁盘上存的字节,再重新编码成 utf8mb4。但如果你实际存的是 UTF-8 编码的中文(只是表定义错写成 latin1),MySQL 就会把两个字节的 UTF-8 中文当成两个独立的 latin1 字符乱解,结果必然变 ? 或乱码。
本质是 MySQL 把“字节误当字符”做了二次转码。真正安全的做法是跳过字符解释环节——让数据以原始字节形式暂存,再声明“这些字节其实是 utf8mb4 编码”。
必须用三步转换法:BLOB 是关键中间态
这三步不触发任何字符解码,纯字节拷贝,零风险:
-
ALTER TABLE t MODIFY col BLOB;—— 必须用MODIFY(不是CHANGE),避免意外重命名;BLOB告诉 MySQL:“别管这是什么字符,就当它是二进制流” - 若原列是
TEXT,对应改用MEDIUMBLOB或LONGBLOB,保持容量一致 - 如果该列上有索引,需先
DROP INDEX,改完再重建;BLOB本身不支持前缀索引以外的索引
从 BLOB 改回 VARCHAR 并指定 utf8mb4
这步才真正完成语义转换:你明确告诉 MySQL “这些字节应被当作 utf8mb4 来读”。
ALTER TABLE t MODIFY col VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;- 务必显式写
COLLATE,否则可能继承库默认(如已弃用的utf8mb4_general_ci) - 长度值(如
255)要和原来一致;如果原是VARCHAR(100),这里也写100,别盲目放大 - 如果原字段允许
NULL,这步要补上NULL或NOT NULL,否则约束会丢失
做完必须校验:HEX 对比才是金标准
迁移后只看显示是否正常远远不够。容易被忽略但最关键的动作是:
-
SELECT HEX(col)对比迁移前后同一行的十六进制值。如果完全一致,说明字节没被改动 - 再
SELECT col看是否显示正常 - 两者都通过才算真正无损
- 如果数据库里混着多种编码(比如部分字段是 GBK、部分是 UTF-8),三步法也无法自动识别——你得先统一源头,否则只能人工分批处理
另外,innodb_large_prefix = ON 和 ROW_FORMAT = DYNAMIC 必须提前开启,否则 VARCHAR 超过 191 字符时建索引会失败。











