导语:字符集问题永远在"看不见的地方"
数据库里最诡异的线上故障,往往不是慢查询,而是字符集不一致:某天接口突然把表情存成 ??、用户昵称变 大家、按拼音排序乱掉。这类问题查起来费劲,因为它们通常不是一条 SQL 的问题,而是建库、建表、连接、客户端、应用五层的字符集没对齐。
一句话总结: 乱码与排序错误的根因 90% 是「连接层字符集 ≠ 存储层字符集」;把每一层都固定为 utf8mb4,问题自动消失。
1. utf8 vs utf8mb4:一字之差,一库之差
1.1 MySQL 的 utf8 是"残缺"的
MySQL 里的 utf8(utf8mb3)最多只能存 3 字节,而 emoji 和部分生僻汉字需要 4 字节。存不下就报错或变成 ??。
| 字符集 | 最大字节 | 能存 emoji | 能存生僻字 | 备注 |
|---|---|---|---|---|
| utf8(utf8mb3) | 3 字节 | ❌ | ❌ | MySQL 历史遗留 |
| utf8mb4 | 4 字节 | ✅ | ✅ | 现代标配 |
| latin1 | 1 字节 | ❌ | ❌ | 旧系统 |
| gbk | 2 字节 | ❌ | 部分 | 中文业务早期 |
-- ❌ utf8 存 emoji 的表现
INSERT INTO users(nickname) VALUES('👋'); -- 变成 '?' 或报错
-- ✅ utf8mb4 正常存储
INSERT INTO users(nickname) VALUES('👋'); -- 正常
1.2 全链路 utf8mb4 标准配置
一句话总结: 从建库到建表到连接,一律
utf8mb4;单点 utf8 就是埋雷。
-- 建库
CREATE DATABASE app DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
-- 建表(列级/表级都显式声明,避免继承混乱)
CREATE TABLE users (
id BIGINT PRIMARY KEY,
nickname VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
) DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 连接层(关键:JDBC 显式指定)
jdbc:mysql://host/db?characterEncoding=UTF-8&connectionCollation=utf8mb4_unicode_ci
2. 排序规则(Collation)选型
排序规则决定了比较、排序、索引选择的行为。同一个字符集下有多个 collation,差别不小。
2.1 utf8mb4 常用 collation 对比
| Collation | 特点 | 适用 |
|---|---|---|
| utf8mb4_general_ci | 老默认,忽略大小写,但拼音/字符排序不够严谨 | 兼容老系统 |
| utf8mb4_unicode_ci | 更准确的 Unicode 排序(基于 UCA),略慢 | 追求正确排序 |
| utf8mb4_0900_ai_ci | MySQL 8.0 新默认,基于 Unicode 9.0,更快更准,支持大小写+重音不敏感 | 新项目首选 |
| utf8mb4_bin | 二进制精确比较,区分大小写 | 需要精确匹配/校验 |
-- 同一个 'abc' 在不同 collation 下
SELECT 'A' = 'a' COLLATE utf8mb4_bin; -- 0(区分大小写)
SELECT 'A' = 'a' COLLATE utf8mb4_general_ci; -- 1(不区分)
2.2 排序规则与索引的关系
索引的有序性由 collation 决定。如果查询要求按特定规则排序(如拼音、字典序),collation 不一致会导致优化器放弃索引走 filesort,甚至结果与期望不符。
-- 按中文拼音排序:需要正确的 collation(如 utf8mb4_0900_ai_ci 对中文拼音有一定支持)
SELECT name FROM users ORDER BY name COLLATE utf8mb4_unicode_ci;
-- 若列是 general_ci,则 ORDER BY name 可能与界面期望的拼音序不一致
一句话总结: 新库用
utf8mb4_0900_ai_ci,老系统保持general_ci兼容;需要精确比较时用utf8mb4_bin。
3. 乱码产生的完整链路
乱码不是数据库"单方面"的问题,是从输入到存储到展示整条链路上的字符集错位。典型链路:
用户输入(👋)
→ HTTP 请求(UTF-8 bytes: F0 9F 91 8B)
→ 应用读取(按 utf-8 解码成字符串)
→ JDBC 连接(characterEncoding)
→ 数据库连接层(set names)
→ 存储到列(列 charset)
→ 返回读取
→ 页面/接口输出(Content-Type: charset=utf-8)
只要其中任何一层的字符集不是 utf8mb4/utf-8,就出现 ? / 乱码 / 丢字。
3.1 最常见的三类乱码
| 乱码形态 | 根因 | 修复 |
|---|---|---|
大家(两个中文变两个西方乱码) | UTF-8 字节被按 latin1 读 | 连接层 set names utf8mb4 |
??(问号) | 4 字节 emoji 存进 utf8 列,或客户端给的是非 utf-8 | 列改 utf8mb4 |
锟斤拷 | UTF-8 被按 GBK 读 | 统一 utf-8 解码 |
3.2 排查步骤
-- 1) 看列/表/库的字符集
SHOW CREATE TABLE users\G
SHOW FULL COLUMNS FROM users;
-- 2) 看当前连接字符集(重要!)
SHOW VARIABLES LIKE 'character_set%';
SHOW VARIABLES LIKE 'collation%';
-- 3) 验证存储是否真的 4 字节 emoji
SELECT nickname, HEX(nickname) FROM users WHERE id=1;
-- 👋 的 UTF-8 应为 F0 9F 91 8B(4 字节)
一句话总结: 乱码排查先确认「存储层能不能存、连接层认不认、展示层怎么读」三层,再从 HEX 验证真相。
4. 连接层字符集陷阱
4.1 character_set_client / character_set_connection
数据库连接建立后,有三个连接相关变量决定字节如何解释:
character_set_client ← 客户端发来的字节按什么解码
character_set_connection ← 服务端内部用什么做运算/比较
character_set_results ← 结果返回给客户端按什么编码
三条原则:三者都要 utf8mb4;set names 一次统一
SET NAMES utf8mb4; -- 等价于设置上面三个变量为 utf8mb4
4.2 常见"设置失效"原因
| 现象 | 原因 | 对策 |
|---|---|---|
| 应用连库乱码但 Navicat 正常 | JDBC 连接串没配 characterEncoding | URL 加 ?characterEncoding=UTF-8 |
| 只建表 utf8mb4 但连接 latin1 | 连接层覆盖存储层 | 统一 set names / JDBC 参数 |
| emoji 存成 ? 但查询正常 | 连接 OK 但列还是 utf8 | 改列、改表为 utf8mb4 |
| 备份还原乱码 | mysqldump 与还原端字符集不一致 | 显式 --default-character-set=utf8mb4 |
5. 字符集迁移与避坑清单
5.1 把已有表从 utf8 升级 utf8mb4
-- 逐表转换(会锁表/重建,需评估窗口)
ALTER TABLE users CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 批量脚本思路:information_schema 拼 ALTER
SELECT CONCAT('ALTER TABLE ', table_name,
' CONVERT TO CHARACTER SET utf8mb4;')
FROM information_schema.tables
WHERE table_schema='app' AND table_collation NOT LIKE 'utf8mb4%';
注意:转换是 DDL 重建,大表要配合在线 DDL/维护窗口,避免高峰期。
5.2 避坑清单
| 坑 | 后果 | 对策 |
|---|---|---|
| 建库 utf8、建表 utf8mb4 | 继承混乱、索引字符集不一致 | 全部显式 utf8mb4 |
| 连接串没配 charset | 写对了存储层也照样乱码 | JDBC/SDK 显式指定 |
| emoji 存报错 | 用户昵称/评论失败 | 列改 utf8mb4 |
| 备份/还原 charset 不一致 | 迁移后乱码 | 两边 set names utf8mb4 |
| 排序规则混用 | 索引失效 / 排序结果错 | 库表列统一 collation |
6. 总结
字符集看似小事,实则是数据质量的根基:
| 环节 | 要点 |
|---|---|
| 存储 | utf8mb4,能存 4 字节 emoji 与生僻字 |
| 排序 | 新库 utf8mb4_0900_ai_ci,精确比较用 _bin |
| 连接 | JDBC characterEncoding + SET NAMES utf8mb4 |
| 排查 | SHOW VARIABLES + HEX 验证存储真相 |
| 迁移 | CONVERT TO utf8mb4,大表配合在线 DDL |
落地记住五件事:统一 utf8mb4、选对 collation、连接层显式指定、用 HEX 验证真相、迁移先查两端字符集。字符集五层对齐,乱码与排序问题就从"线上事故"变成"可避免的常识"。
延伸阅读
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。