乱码本质是字节在某层被错误解码,必须确保character_set_client、character_set_connection、character_set_results三者全为utf8mb4;仅改表结构或用CONVERT属治标,源头须统一连接参数与字段定义。

乱码不是“显示问题”,而是字节在某一层被错误解码了——查出来是 æäº› 或 ???,说明原始 UTF-8 字节早已被当成了 latin1 或 GBK 解析;此时改字体、换客户端、甚至用 CONVERT 都只是在错误结果上打补丁。
确认当前连接实际生效的字符集三件套
别只看建表语句或数据库默认值。执行:
SHOW VARIABLES LIKE 'character_set%';
重点关注这三项是否**全为** utf8mb4:
-
character_set_client:你发的 SQL 里中文字符串按什么编码解析 -
character_set_connection:MySQL 内部转换用的中间编码 -
character_set_results:返回给你的结果按什么编码打包
任一不是 utf8mb4,SELECT 出来的中文就可能出问题。常见陷阱:character_set_client 是 latin1,哪怕字段定义是 utf8mb4,插入的 '张三' 也会被当成两个 latin1 字符存进去。
连接初始化时必须显式声明 utf8mb4,不能依赖服务端配置
MySQL 启动时的全局设置会被连接层覆盖。不同场景写法不同:
- Navicat:右键连接 →「编辑连接」→「高级」→ 勾选「使用 MySQL 字符集」,下拉框选
utf8mb4(不是utf8) - 命令行:
mysql --default-character-set=utf8mb4 -u root -p - Python + pymysql:
pymysql.connect(..., charset='utf8mb4') - JDBC URL:
jdbc:mysql://host/db?useUnicode=true&characterEncoding=utf8mb4 - PHP PDO DSN:
mysql:host=localhost;charset=utf8mb4
漏掉任意一项,SET NAMES utf8mb4 只是临时补救,且必须在首次查询前执行。
GROUP_CONCAT 或视图里中文变乱码?优先查连接层,不是字段定义
GROUP_CONCAT 和视图不读字段的 CHARACTER SET,它只继承当前 session 的 character_set_connection。所以单条查正常、一聚合就变 ????,基本锁定在这儿。
- 执行
SHOW VARIABLES LIKE 'character_set%',确认三者全为utf8mb4 - 避免写
GROUP_CONCAT(CONVERT(col USING utf8mb4))—— 拼接动作本身已在错误编码下完成,再转已晚 - 正确写法:
GROUP_CONCAT(CAST(col AS CHAR CHARACTER SET utf8mb4)) - 视图同理:即使底层表字段是
utf8mb4,只要连接层没对齐,视图查出来照样乱码
CONVERT 不是修复工具,是调试和清洗手段
CONVERT(col USING utf8mb4) 看起来“正常”反而是危险信号。典型现象:SELECT name 返回 æäº›,但 SELECT CONVERT(name USING utf8mb4) 返回 某些 —— 这说明原始字节已被错误解码过一次,CONVERT 只是强行把错字再映射回去,纯属巧合。
- 验证是否双编码:执行
SELECT HEX(name), CHAR_LENGTH(name),若前者长度是后者的两倍,基本确认是 latin1 解析 UTF-8 字节导致的双编码 -
CONVERT只适合补救性场景:比如老表字段是latin1,但当年插的是 UTF-8 字节,现在导出到新表时做一次性清洗 - 真要修复,得从源头入手:改连接参数、改字段定义、重插数据,而不是靠表达式掩盖
最易被忽略的一点:Navicat 的「查询结果缓存」会固化旧编码解析结果。关掉「工具 → 选项 → 查询 → 启用查询结果缓存」,再重新执行——很多“重启无效”的情况卡在这儿。

















