一、utf8 是个历史遗留
MySQL 的 utf8 别名指向 utf8mb3:每个字符最多 3 字节,只覆盖基本多文种平面(BMP)。而 emoji、部分生僻字在 4 字节区,所以:
-- utf8mb3 列插入 emoji
INSERT INTO t (name) VALUES ('👍');
-- ERROR 1366: Incorrect string value: '\xF0\x9F\x91\x8D'
utf8mb4 才是完整的 UTF-8(最多 4 字节)。新项目一律用 utf8mb4,没有例外。MySQL 8.0 已把默认字符集改为 utf8mb4,但老库升级后仍需逐表检查。
二、字符集与排序规则
字符集决定「能存什么」,排序规则(collation)决定「怎么比较和排序」。
| collation | 特点 |
|---|---|
utf8mb4_general_ci | 老默认,比较快,规则较粗 |
utf8mb4_unicode_ci | 基于 Unicode 规则,比较准确 |
utf8mb4_0900_ai_ci | MySQL 8.0 默认,基于 UCA 9.0,推荐 |
utf8mb4_bin | 按字节比较,区分大小写 |
ci 是 case-insensitive(不区分大小写),ai 是 accent-insensitive(不区分重音)。做用户名、邮箱唯一索引时要注意:ci 下 Admin 和 admin 视为相同,这通常正是想要的效果;但做验证码、token 比较时应该用 _bin。
三、四个层级的字符集
字符集可以设在服务器、数据库、表、列四级,就近生效:
SHOW VARIABLES LIKE 'character_set%'; -- 服务器与连接
SHOW CREATE TABLE orders; -- 看表与列的
常见坑:库是 utf8mb4,但某个列建表时被指定成 utf8,于是只有这一列存不了 emoji。排查时要看到列级。
连接字符集也很关键:
[client]
default-character-set = utf8mb4
[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_0900_ai_ci
JDBC 连接串加 characterEncoding=utf8&connectionCollation=utf8mb4_0900_ai_ci(注意驱动版本差异),PHP 的 PDO 在 DSN 里写 charset=utf8mb4。连接字符集不对,会出现「库里存的是乱码但长度正常」的情况,比直接报错更难查。
四、把老表改成 utf8mb4
ALTER TABLE orders CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
注意三点:
CONVERT TO会重建表,大表要用pt-online-schema-change或gh-ost,避免长时间锁表。- 只改表不改列,某些列可能仍是旧字符集,要逐列确认。
- 改之前备份,字符集转换是不可逆的操作(尤其是里面已经有乱码时)。
五、索引长度限制
utf8mb4 每字符 4 字节,VARCHAR(255) 建索引需要 1020 字节。InnoDB 单列索引前缀上限是 3072 字节(DYNAMIC 行格式),所以 VARCHAR(768) 以内可以整列索引,再长就要用前缀索引:
ALTER TABLE t ADD INDEX idx_name (name(191));
191 这个数字来自老版本「767 字节 / 4」的推导,现在虽已放宽,但短前缀索引仍然更省空间。前缀索引的代价是无法用于覆盖索引和 ORDER BY。
六、乱码怎么判断
| 现象 | 原因 |
|---|---|
| 插入报 1366 | 列字符集存不下该字符(多为 utf8mb3 + emoji) |
存进去是 ??? | 连接字符集不支持,被替换 |
读出来是 测试 | UTF-8 字节被按 latin1 解释 |
| 导出文件打开乱码 | 导出时未指定 --default-character-set=utf8mb4 |
备份导出一定要带字符集参数:
mysqldump --default-character-set=utf8mb4 -u root -p db > db.sql
七、实操建议
- 新建库表统一
utf8mb4+utf8mb4_0900_ai_ci。 - 连接串显式声明字符集,不依赖默认值。
- 迁移老库前先统计哪些列存了 4 字节字符(用
CHAR_LENGTH与LENGTH的比值辅助判断)。 - 前后端交互统一 UTF-8,不要中途转 GBK。
字符本身的问题可以用 Unicode 转换工具 查码点和字节,确认到底是 3 字节还是 4 字节字符。
小结
MySQL 的 utf8 = utf8mb3,只有 3 字节,存不了 emoji;新项目一律 utf8mb4;字符集分服务器/库/表/列四级,要看到列级;老表转换会重建表,大表用在线工具。