极简工具箱

免费在线工具 · 本地计算 · 无需上传

MySQL 字符集:utf8 和 utf8mb4 到底差在哪

存 emoji 报 `Incorrect string value`,或者存进去变成 `???`——因为 MySQL 里的 `utf8` 其实是个「阉割版」,最多 3 字节,装不下 4 字节的 emoji。

广告位(百度联盟)

一、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_ciMySQL 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;

注意三点:

  1. CONVERT TO 会重建表,大表要用 pt-online-schema-change 或 gh-ost,避免长时间锁表。
  2. 只改表不改列,某些列可能仍是旧字符集,要逐列确认。
  3. 改之前备份,字符集转换是不可逆的操作(尤其是里面已经有乱码时)。

五、索引长度限制

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

七、实操建议

字符本身的问题可以用 Unicode 转换工具 查码点和字节,确认到底是 3 字节还是 4 字节字符。

小结

MySQL 的 utf8 = utf8mb3,只有 3 字节,存不了 emoji;新项目一律 utf8mb4;字符集分服务器/库/表/列四级,要看到列级;老表转换会重建表,大表用在线工具。

本文相关工具(本地计算,无需上传)

更多使用教程