必须用“三步转换法”:先转blob,再转varchar并指定utf8mb4字符集,中间不经过任何解码;因直接convert会按原声明字符集(如latin1)错误解读实际字节,导致中文变?。

直接改 ALTER TABLE ... CONVERT TO CHARACTER SET utf8mb4 会把原有中文变成 ?,这不是迁移,是破坏。必须用“三步转换法”:先转成 BLOB,再转成 VARCHAR 带新字符集,中间不经过任何解码。
为什么不能直接 CONVERT TO CHARACTER SET utf8mb4
MySQL 在执行 CONVERT TO 时,会按原列声明的字符集(latin1)去“解读”磁盘上存的字节,再重新编码为 utf8mb4。但如果你实际存的是 GBK 或 UTF-8 编码的中文(只是表定义写成了 latin1),它就会把两个字节的中文当成两个独立的 latin1 字符乱解,结果必然变 ? 或乱码。本质是 MySQL 把“字节误当字符”做了二次转码。
真正安全的做法是跳过字符解释环节——让数据以原始字节形式暂存,再声明“这些字节其实是 utf8mb4 编码”,由 MySQL 重新按 utf8mb4 解析。
MODIFY ... BLOB 是关键中间态
把字段临时改成 BLOB 类型,相当于告诉 MySQL:“别管这是什么字符,就当它是二进制流”。这一步不触发任何字符转换,纯字节拷贝,零风险。
-
ALTER TABLE t MODIFY col BLOB;—— 必须用MODIFY(不是CHANGE),避免意外重命名 - 如果原列是
TEXT,对应改用MEDIUMBLOB或LONGBLOB,保持容量一致 - 若列上有索引,需先
DROP INDEX,改完再重建;BLOB本身不支持前缀索引以外的索引
再从 BLOB 改回 VARCHAR 并指定 utf8mb4
这步才真正完成语义转换:你明确告诉 MySQL “这些字节应被当作 utf8mb4 来读”。只要原始字节确实是 utf8mb4 编码(或兼容的如 GBK、UTF-8),就能正确还原。
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,否则约束会丢失
容易被忽略的校验和边界
做完三步后,SELECT HEX(col) 对比迁移前后同一行的十六进制值。如果完全一致,说明字节没被改动;再 SELECT col 看是否显示正常。两者都通过才算真正无损。
- 如果数据库里混着多种编码(比如部分字段是 GBK、部分是 UTF-8),三步法也无法自动识别——你得先统一源头,否则只能人工分批处理
-
innodb_large_prefix = ON和ROW_FORMAT = DYNAMIC必须提前开启,否则VARCHAR超过 191 字符的索引会失败 - 连接层也要同步改:
SET NAMES utf8mb4或应用配置里把charset设为utf8mb4,否则新插入数据仍可能走错路径











