必须统一客户端、连接层、表结构三层字符集为utf8mb4;仅改character_set_server无效,关键要设置应用连接参数charset=utf8mb4并用HEX()验证真实存储编码。

只改 character-set-server=utf8mb4 不足以解决中文乱码,甚至可能掩盖真正的问题。 乱码几乎总是发生在客户端连接层(character_set_client/character_set_connection/character_set_results)与表结构字符集不一致时,而非服务器默认值本身。
为什么改了 character-set-server 还是乱码
这个配置只影响新创建的数据库的默认字符集,对已有库、表、连接行为完全无影响。更关键的是:客户端驱动在 TCP 握手阶段会主动声明自己使用的字符集,这个声明直接覆盖 character-set-server 的值。比如 Python 的 pymysql.connect() 不传 charset='utf8mb4',即使服务端设成 utf8mb4,连接进来时三值仍可能是 latin1。
- 执行
SHOW VARIABLES LIKE 'character_set%';后,重点看character_set_client、character_set_connection、character_set_results—— 它们必须都是utf8mb4 - 如果这三项是
latin1或gbk,说明客户端没声明或声明错了,此时改my.cnf里的character-set-server毫无意义 -
character_set_server只在创建新库且未显式指定CHARACTER SET时起作用;建表时也完全不继承它
正确设置客户端连接字符集(比改配置文件更优先)
应用代码里显式声明字符集,比修改 my.cnf 更可靠、更直接:
- Python + PyMySQL:
pymysql.connect(..., charset='utf8mb4')—— 漏掉这句,连上来就是latin1 - Java + JDBC:
jdbc:mysql://host/db?characterEncoding=utf8mb4&serverTimezone=UTC——characterEncoding参数不能省 - PHP + PDO:
mysql:host=localhost;dbname=test;charset=utf8mb4—— DSN 中的charset是必须项 - 命令行客户端:
mysql -u root -p --default-character-set=utf8mb4 db_name,或在~/.my.cnf的[client]段写default-character-set = utf8mb4
已有表必须显式转换,不能依赖全局配置
character-set-server 对旧表零影响。已有表若原为 latin1 或 utf8(即 utf8mb3),必须手动改:
- 先确认字段长度是否安全:
VARCHAR(255)在utf8mb4下最多占 1020 字节,而 InnoDB 索引前缀限制旧版是 767 字节 —— 若报Specified key was too long,得先缩字段,如MODIFY name VARCHAR(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci - 用
ALTER TABLE t CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;会重建整张表,大表要停写 -
CONVERT TO和MODIFY COLUMN效果不同:前者改数据+结构+索引,后者只改列定义,容易漏掉排序规则或索引兼容性问题
验证是否真“存对了”,别信 SELECT 显示结果
有些客户端(如老版 phpMyAdmin、某些终端)会自行转码,导致“看着对,其实错”。真实存储编码要用 HEX() 检查:
- 插入后执行
SELECT HEX(name) FROM t WHERE id = 1;—— 正确的中文 UTF-8 字节序列应是类似E68891(“我”)这样的 6 字节十六进制串 - 如果看到
C4E3BAC3这类 8 字节串,大概率是gbk被当utf8mb4解码了 - 如果
HEX()结果正常但 SELECT 显示为???或 Mojibake(如æ‘),说明读取解码环节出错,重点查character_set_results
最常被忽略的点是:**连接参数 > 配置文件 > 表定义**。哪怕你把所有层级都设成 utf8mb4,只要应用连接时没传 charset,乱码就一定会发生。











