MySQL 中文乱码的三种成因与定位方法
“库和表都设成 utf8mb4 了,为什么写进去还是乱码?”——这个问题我遇到过好几次,
每次原因都不一样。这篇把三种常见的成因拆开,各给一条可以直接复现的定位方法。
先建立一个判断框架
乱码的本质是:同一段字节被用不同的字符集解释。所以要定位,就要先回答一个问题: 乱码是发生在“写入时”,还是“读取时”,还是“写进去的时候就已经错了”?
最省事的判别方法是把库里存的字节直接取出来看十六进制:
SELECT HEX(字段名) FROM 表名 WHERE id = 1;
拿“中”字举例,UTF-8 编码是 E4B8AD。如果查出来就是 E4B8AD,
说明存的是对的,问题在读取或展示环节;如果查出来像 C3A4C2B8C2AD 这种明显变长的,
那就是写进去之前就已经被错误编码过了。
成因一:客户端字符集不是 utf8mb4
最常见的一种。用命令行客户端导入 SQL 文件时,如果不显式指定字符集,客户端会按默认字符集 解析文件里的字节,再转成服务端字符集写库,中文就被“二次编码”了。
# 错误写法:依赖客户端默认字符集
mysql -uroot -p 库名 < init.sql
# 正确写法:显式指定
mysql --default-character-set=utf8mb4 -uroot -p 库名 < init.sql
典型特征是“注释、菜单名、权限名这些静态文案全乱,而用户填的数据不乱”—— 因为只有脚本导入的那批数据走了客户端字符集。
入库后怎么核对?用字符数与字节数交叉验证,比肉眼看更可靠:
-- 「隐私」两个汉字:字符数应为 2,UTF-8 字节数应为 6
SELECT CHAR_LENGTH(字段名), LENGTH(字段名) FROM 表名 WHERE id = 1;
成因二:连接字符集与会话不一致
应用侧连接串没声明字符集时,驱动可能按服务端默认值协商,结果写入用的字符集和表定义不一致。 这类问题的特征是:命令行里写进去正常,应用写进去乱码(或反过来)。
-- 查看当前会话的字符集相关变量
SHOW VARIABLES LIKE 'character_set%';
修法是两头都固定住:连接串显式带上字符集参数,服务端配置里也别留旧默认值。 改完记得让连接池重建连接,否则旧连接的字符集不会变。
成因三:已经是乱码的数据被再次转换
这种情况最麻烦:数据在某次迁移中被“修”过一次,但用的是错的字符集去转,
于是从“一次乱码”变成“二次乱码”。特征是乱码里出现大量
Ã、å、é 这类拉丁扩展字符。
判断方法:把可疑字符对照 cp1252 表手工反推一次,看能不能还原成合理的 UTF-8 字节序列。 能还原说明还有救(把字节按正确字符集重新解释即可);不能还原就只能从备份或日志里找原始数据。
排查顺序建议
- 先
HEX()看库里存的字节,确定“写入时错”还是“读取时错”; - 确认导入/连接是否显式指定了
utf8mb4; - 用
CHAR_LENGTH与LENGTH交叉验证,别靠肉眼; - 全库范围排查时,用正则匹配乱码特征字符,一次扫完所有文本列。
最后一条经验:任何写中文的 SQL 都显式带字符集参数。 这一点点麻烦,比事后逐条修复便宜太多。
← 返回首页