必须手动确认每张表真实字符集,而非依赖server默认值;执行show create table和查询information_schema.tables、columns,导出时须用--default-character-set指定源编码,alter table convert会重编码数据,导入后乱码需检查连接层、sql模式及排序规则兼容性。

MySQL升级到8.0后,utf8mb4已是默认字符集,但旧表不会自动变——迁移不是“设个参数就完事”,而是必须手动处理每张表的真实编码状态,否则中文、emoji写入报1366 Incorrect string value或显示为问号。
怎么确认表到底用的是什么字符集?
别只看character_set_server,它只影响新库新表。真正要查的是表本身的实际定义:
- 运行
SHOW CREATE TABLE t1;,看输出里是否含CHARACTER SET latin1或CHARACTER SET utf8(注意:这里的utf8是utf8mb3,不支持emoji) - 执行
SELECT TABLE_NAME, TABLE_COLLATION FROM information_schema.TABLES WHERE TABLE_SCHEMA = 'db_name';,快速扫一遍所有表的排序规则 - 重点检查
information_schema.COLUMNS里character_set_name列,有些表字段级字符集和表级不一致,会埋坑
mysqldump导出时为什么必须指定--default-character-set?
因为mysqldump默认按连接字符集读数据——如果源表是latin1,而你没加--default-character-set=latin1,它就会把原本存成E4(ä)的字节,强行当utf8mb4解码成乱码,再写进dump文件。导入后彻底不可逆。
- 导出
latin1表:mysqldump -u root -p --default-character-set=latin1 db_name > dump.sql - 导出
utf8(即utf8mb3)表:mysqldump -u root -p --default-character-set=utf8 db_name > dump.sql - 导入前用
head -15 dump.sql | grep CHARSET确认CREATE语句里的字符集声明没被意外改掉
ALTER TABLE CONVERT TO CHARACTER SET utf8mb4到底做了什么?
它不是改个元数据那么简单——MySQL会逐行读取数据,先按原字符集(比如latin1)解码字节流,再按utf8mb4重新编码写回磁盘。这意味着:
- 表必须有足够空间:如果原字段是
VARCHAR(255)且用了utf8mb4,索引长度可能超767字节限制,得提前开innodb_large_prefix=ON - 不能在从库上直接跑:主从复制可能因字符集转换触发SQL线程中断,建议停写或切到单点操作
- 对大表要评估锁时间和IO压力,
CONVERT会重建整张表,不是在线DDL
为什么导入后还是乱码?三个最容易漏的点
迁移做完不代表万事大吉,连接层、应用层、SQL模式全得对齐:
- 客户端连接没显式声明:
mysql -u user -p --default-character-set=utf8mb4 db_name,或者JDBC URL里加?characterEncoding=utf8mb4 - SQL模式不兼容:MySQL 8.0默认启用
STRICT_TRANS_TABLES,而老表建表语句里可能有DEFAULT '0000-00-00',导入时直接报ERROR 1067,得加--init-command="SET sql_mode=''" - 排序规则混用:
utf8mb4_0900_ai_ci是8.0默认,但5.7不认识,跨版本迁移时得统一换成utf8mb4_unicode_ci或utf8mb4_general_ci
最麻烦的不是操作步骤,而是判断“这行数据现在到底是什么编码”——尤其当历史库存在“latin1存UTF-8字节”的情况时,SHOW CREATE TABLE看到的是latin1,但实际内容是UTF-8编码的字节。这种隐性错配只能靠抽样校验+原始业务日志交叉比对,没法靠一条命令自动修复。











