乱码根本原因是客户端与服务器字符集协商失败,必须确保character_set_client、character_set_connection、character_set_results三者均为utf8mb4;需通过show variables like 'character_set%'实时检查,连接时强制指定charset参数,并在建表和存储过程中显式声明utf8mb4。

乱码不是数据库“存错了”,而是客户端连上来时用的编码和服务器预期不一致——character_set_client、character_set_connection、character_set_results 这三项必须全为 utf8mb4,缺一不可。
检查当前连接的字符集三值是否一致
别猜,直接查。进 MySQL 后执行:
SHOW VARIABLES LIKE 'character\_set%';
重点看这三项:
-
character_set_client:客户端发过来的字节按什么编码解析 -
character_set_connection:SQL 字符串在服务端内部转成什么编码做比较 -
character_set_results:结果集返回给客户端时用什么编码打包
只要其中任意一个不是 utf8mb4,中文就可能变问号或 Mojibake(比如 æäº›æå)。常见错误是三者混着来:client=latin1、connection=utf8mb4、results=gbk——这种组合必乱。
客户端连接参数比配置文件更优先
my.cnf 里写了 default-character-set=utf8mb4 没用,如果驱动没主动声明,MySQL 就按握手协议默认值走(老版本常是 latin1)。
实操建议:
- 命令行连接时加参数:
mysql -u root -p --default-character-set=utf8mb4 - Python
pymysql.connect()必须显式传charset='utf8mb4' - JDBC URL 加参数:
?useUnicode=true&characterEncoding=utf8mb4 - PHP PDO DSN 后追加:
;charset=utf8mb4
漏掉任何一处,SET NAMES utf8mb4 都只是临时补救,且不能覆盖已发生的解析错误(比如建存储过程时的中文注释)。
创建/修改表时必须显式指定 utf8mb4
数据库级默认值(character_set_server)不会自动继承到表和列。即使你改了 my.cnf,新建表仍可能用 latin1。
正确写法:
CREATE TABLE t (name VARCHAR(100)) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
已有表转换要小心:
-
ALTER TABLE t CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;—— 全量重建,大表会锁表 - 只改列定义(如
MODIFY name VARCHAR(100) CHARACTER SET utf8mb4)不够,索引前缀长度可能被截断 - 转换前务必备份,
CONVERT TO对已有乱码数据无效
存储过程和 SQL 文件里的中文最容易踩坑
存储过程体内的中文字符串(如 SELECT '你好' 或注释)是在创建时被解析的,不是执行时。如果当时 character_set_client 是 latin1,那两个汉字字节就被当 latin1 解析并存进 mysql.proc 表,永久损坏。
安全做法:
- 所有
CREATE PROCEDURE前加一行:SET NAMES utf8mb4; - SQL 文件保存为 UTF-8 编码(无 BOM),且首行插入
SET NAMES utf8mb4; - 避免依赖
[mysql]段的default-character-set——它只影响mysql命令行工具,不影响其他客户端
真正容易被忽略的是:哪怕服务器、数据库、表全设成 utf8mb4,只要连接那一刻没协商对编码,后续所有操作都从根上错了。











