三层字符集与错的环节
MySQL 处理写入时按如下顺序:把客户端发来的字节按 character_set_client 解释成字符,再按 character_set_connection 转换,最后存进列自身字符集。取数据时反向按 character_set_results 输出。乱码的根因几乎总是「客户端实际发的字节」与它声明的编码不一致,例如驱动发 UTF-8 字节却声明 latin1,于是每个汉字都被当成两个西欧字符再转存,形成双重编码。排查顺序是先看变量、再看列定义、最后核对驱动参数。
基本信息
| 连接层变量 | character_set_client、character_set_connection、character_set_results |
|---|---|
| 服务器与库 | character_set_server、character_set_database,影响新建库表的默认值 |
| 列定义 | 建表时指定的 charset 与 collation,决定实际存储 |
| 排序规则 | collation 影响排序与比较,不一致时关联查询会报混用排序规则 |
| 驱动参数 | 命令行 --default-character-set;JDBC 的 characterEncoding;连接后 SET NAMES |
| 常见历史债 | latin1 列里存着 UTF-8 字节,取出来是重音怪串 |
典型使用场景
- 新项目建库:统一 utf8mb4 与配套排序规则,避免后期改表
- 迁移摸底:统计哪些库表的字符集与主流程不一致
- 乱码定位:区分是连接层解释错,还是列里本来存的就是错的字节
- emoji 支持:确认列与连接都支持 4 字节字符,否则插入直接失败
- 导入导出:mysqldump 与客户端参数保持一致,避免往返变形
排查与设置示例
排查与设置示例
-- 1) 先看连接层与服务器层变量
SHOW VARIABLES LIKE 'character_set%';
SHOW VARIABLES LIKE 'collation%';
-- 2) 再看目标表的列定义与排序规则
SHOW FULL COLUMNS FROM orders;
SHOW CREATE TABLE orders;
-- 3) 连接建立后统一声明(影响 client/connection/results 三者)
SET NAMES utf8mb4;
-- 4) 抽样验证:统计可疑内容与长度差异
SELECT COUNT(*) FROM orders WHERE remark LIKE '%Ã%';
SELECT HEX(remark) FROM orders LIMIT 3;变量与作用对照
| 变量/参数 | 作用 | 排查要点 |
|---|---|---|
| character_set_client | 解释客户端发来的字节 | 与实际发送编码不符即产生双重编码 |
| character_set_connection | 连接内转换使用的字符集 | SET NAMES 会同时设置它 |
| character_set_results | 返回给客户端时的字符集 | 与客户端读取方式不符会显示乱码 |
| character_set_database | 库默认字符集 | 改库不改表时旧表仍是原字符集 |
| collation_* | 排序与比较规则 | 关联查询报混用排序规则时需统一 |
| 列定义 charset | 数据实际存储编码 | 历史列可能是 latin1,需单独处理 |
utf8 与 utf8mb4 在 MySQL 里含义不同:前者最多 3 字节,无法存 emoji 与增补平面字符;新库应直接使用 utf8mb4。旧列若已是 utf8,需要通过改列定义(ALTER TABLE ... CONVERT TO CHARACTER SET)来升级,操作前务必备份。
改字符集与修数据是两件事:如果列里存的本来就是按错误方式编码过的字节,只改字符集不会自动还原内容,需要先按字节还原再改列定义。两件事同时做,很容易把可逆乱码变成不可逆损坏,建议分两步并逐步验证。
连接参数要贯穿全链路:客户端库、连接池、ORM 配置、导入导出工具都应指定同一字符集。只在代码里 SET NAMES,而备份工具按另一字符集导出,会得到一份本身就不一致的备份。