必须用 alter table ... convert to character set 而非 character set,因后者仅修改默认字符集声明而不转换数据,易致双编码乱码;convert to 才真正重解码并重编码文本字段内容。

直接用 ALTER TABLE ... CONVERT TO CHARACTER SET,但必须确认当前数据编码真实、目标字符集兼容、索引长度没超限——否则不是改错就是报错。
为什么不能只用 ALTER TABLE ... CHARACTER SET?
这条语句(如 ALTER TABLE t1 CHARACTER SET utf8mb4)只改表默认字符集声明,不碰已有字段内容。如果字段实际存的是 latin1 编码的字节流,而你只改了声明,查询时 MySQL 会按 utf8mb4 解码,结果就是双编码乱码(比如“中文”变成“涓枃”)。CONVERT TO 才真正重写字段内容,把存储字节按原字符集解读后,再用目标字符集重新编码。
CONVERT TO 会动哪些东西?
它不是“改个配置”,而是重建表结构+重写文本列数据:
- 修改表默认字符集和排序规则(
TABLE_COLLATION) - 对所有
CHAR、VARCHAR、TEXT类型字段:读取当前字节 → 按原字符集解码 → 按新字符集重新编码 → 写回磁盘 - 不改动
INT、BLOB、JSON等非文本字段 - 不显式修改索引定义,但若索引字段是文本类型,其内部排序规则会随列字符集隐式更新
- 触发表重建(MySQL 5.7+ 对部分引擎支持
ALGORITHM=INPLACE,但字符集转换基本走COPY,锁表时间取决于表大小)
执行前必须检查的三件事
漏掉任何一项都可能让数据变乱码或语句失败:
-
确认字段真实编码:查
SHOW CREATE TABLE t1,看字段当前COLLATE是什么;再用SELECT HEX(col), col FROM t1 LIMIT 1看原始字节。比如latin1字段里存了 UTF-8 字节流(常见于旧应用),直接CONVERT TO utf8mb4会二次解码,必须先用CONVERT(CONVERT(col USING latin1) USING utf8mb4)修复 -
检查索引前缀长度:从
utf8(即utf8mb3)转utf8mb4后,TEXT列索引前缀上限从 255 降为 191。如果已有索引用了text_col(255),CONVERT TO会报错Specified key was too long,得先DROP INDEX或改前缀 -
验证目标字符集是否覆盖源内容:比如字段用
gbk存了中文,想转utf8mb4没问题;但如果反过来,utf8mb4字段里有 emoji,转成gbk就必然丢数据
批量改多个表怎么写 SQL?
别手动拼,用 information_schema 生成:
SELECT CONCAT('ALTER TABLE `', table_schema, '`.`', table_name, '` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;')
FROM information_schema.tables
WHERE table_schema = 'your_db'
AND engine = 'InnoDB'
AND table_collation NOT LIKE 'utf8mb4%';
注意点:
- 加反引号包裹库名和表名,防关键字冲突
- 过滤掉系统库(
mysql、information_schema等)和非InnoDB表(MyISAM转换行为不一致) -
COLLATE必须显式指定,否则用服务器默认(MySQL 8.0 是utf8mb4_0900_ai_ci,老版本可能不识别) - 生成的语句要先
SELECT ... INTO OUTFILE或复制出来人工检查一遍,确认无误再执行
最易被忽略的是:字符集转换不是纯元数据操作,它依赖你对当前数据真实编码的理解。字段声明和实际存储不一致时,CONVERT TO 不是解药,而是加速器——它会忠实地把错的字节按错的方式再编一次。











