mysql 8.0迁移后旧表字符集不会自动升级,insert emoji报incorrect string value的根源是字段级字符集仍为utf8或latin1;必须显式执行alter table modify column指定character set utf8mb4和collate,并同步校准连接层、row_format及索引限制。

MySQL 8.0 迁移后默认字符集变成 utf8mb4,但旧表一个字节都不会自动改——这意味着你看到的“默认已生效”,和你查到的数据实际用的编码,大概率是两回事。问题不是出在“要不要改”,而是出在“改得不彻底”。
为什么迁移后 INSERT emoji 直接报 Incorrect string value
这不是驱动或应用层的问题,是字段级字符集没升级到位。常见错误路径:
- 只改了数据库级:
ALTER DATABASE db_name CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,但表和列仍是utf8或latin1 - 执行了
ALTER TABLE t CONVERT TO CHARACTER SET utf8mb4,却没加COLLATE,导致列级 collation 被悄悄换成utf8mb4_0900_ai_ci,而应用仍按utf8_bin逻辑写入 - JDBC URL 漏了
&characterEncoding=utf8mb4,连接层用latin1解码传入的??字节,服务端再转成utf8mb4时已损坏
验证方式:对出问题的字段执行 SHOW FULL COLUMNS FROM t LIKE 'col_name',看 Collation 列是不是 utf8mb4_*;同时检查 SELECT @@character_set_client, @@character_set_connection, @@character_set_results 是否全为 utf8mb4。
CONVERT TO CHARACTER SET utf8mb4 为什么会让查询结果变多?
因为 collation 变了,而你没意识到它会影响 WHERE 和 JOIN 的语义。例如:
- 原字段是
VARCHAR(50) CHARACTER SET utf8 COLLATE utf8_bin(区分大小写),WHERE name = 'ABC'只匹配大写 - 执行
CONVERT TO CHARACTER SET utf8mb4后,collation 自动变成utf8mb4_0900_ai_ci(不区分大小写+不区分重音),同一条件会匹配'abc'、'AbC'、'àbc' - 如果该字段上有唯一索引,原来能插入的两条记录(
'ABC'和'abc')现在会因重复键冲突失败
正确做法是显式指定 collation:ALTER TABLE t MODIFY COLUMN name VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;不要依赖 CONVERT TO 的默认行为。
迁移后 checksum 对不上,但 COUNT(*) 一致?
这是字符集隐式转换最典型的“静默破坏”。比如源库字段定义为 VARCHAR(100) CHARACTER SET utf8,目标库同名字段是 VARCHAR(100) CHARACTER SET utf8mb4,导出工具(如 mysqldump)在中间做了自动编码转换,导致二进制内容不同,但字符串看起来一样。
排查建议:
- 别只比行数,对关键业务字段做
CRC32(name)校验(比MD5()快,且避免CHECKSUM TABLE锁表) - 检查 dump 文件里
CREATE TABLE语句是否保留了原始CHARACTER SET,如果是latin1源库,导出必须加--default-character-set=latin1 - 确认迁移前后
SHOW CREATE TABLE输出中,每个字段的CHARACTER SET和COLLATE完全一致
索引失效、锁等待飙升,和 utf8mb4 有什么关系?
utf8mb4 本身不锁表,但它让索引键变宽,进而触发一系列连锁反应:
- 相同
VARCHAR(255)字段,在latin1下索引键最多 255 字节,在utf8mb4下最多 1020 字节——超过 InnoDB 默认 767 字节限制,导致索引被截断或建不起来 - 若未开启
innodb_default_row_format = DYNAMIC,CONVERT TO会退化为 COPY 算法,全程持有 SX 元数据锁,DML 全部阻塞 - 客户端连接未设
charset=utf8mb4,WHERE 条件触发隐式转换,索引失效 → 全表扫描 → 行锁范围扩大 →SHOW ENGINE INNODB STATUS里出现大量LOCK WAIT
真正要盯住的不是字符集名字,而是三件事:字段定义是否显式声明 CHARACTER SET utf8mb4、表 ROW_FORMAT 是否为 DYNAMIC、连接层是否与字段层完全对齐。











