MySQL 字符集 utf8mb4 和 utf8 的区别

FreeGuideOnline 最新 2026-07-06

MySQL 字符集 utf8mb4 与 utf8 的区别:从踩坑到精通

在 MySQL 数据库中,utf8utf8mb4 是两个容易让初学者困惑的字符集。表面上它们都声称支持 UTF-8 编码,但实际行为却大相径庭。本教程将深入剖析两者的本质区别、产生原因、存储机制以及迁移方案,帮助你彻底避开乱码和表情符号丢失的陷阱。


1. 核心区别速览

特性 utf8 (utf8mb3) utf8mb4
全名 UTF-8 的"伪"实现,实际仅支持最多 3 字节 真正的 UTF-8 实现,支持 1~4 字节
最大字符长度 3 字节 4 字节
支持的字符范围 基本多文种平面 (BMP),即 Unicode U+0000 至 U+FFFF 全部 17 个平面,包括 BMP 和补充平面
能否存 Emoji 表情? ❌ 不能。如 😂、👍 等会存储失败或被截断 ✅ 完全支持
能否存一些生僻汉字? ❌ 不能。如 "𠮷"(U+20BB7) ✅ 支持
对应 MySQL 内部代号 utf8mb3 (从 8.0.28 开始,显示为 utf8mb3) utf8mb4
官方建议 已废弃,不再推荐使用 推荐作为所有新项目的默认字符集

一句话总结:MySQL 的 utf8 是一个历史遗留的阉割版字符集,它并不是标准 UTF-8。真正的标准 UTF-8 在 MySQL 中叫 utf8mb4


2. 为什么会有这两个字符集?——历史遗留的巨坑

2.1 历史的错误命名

  • 在 MySQL 早期(4.1 版引入字符集时),当时 Unicode 标准只有基本多文种平面(BMP),所有字符编码都不超过 3 字节。
  • 开发团队认为 UTF-8 编码最多就 3 字节,于是他们创建了一个名为 utf8 的字符集,内部规定每个字符最多使用 3 个字节存储。
  • 后来 Unicode 标准扩展到了补充平面(字符编码超过 U+FFFF),需要使用 4 字节 UTF-8 编码。但此时 MySQL 的 utf8 已经广泛部署,不能直接修改其已定义好的 3 字节限制。
  • 2010 年,MySQL 5.5.3 引入了 utf8mb4(mb4 表示 multi-byte 4),用以支持完整的 UTF-8。

2.2 字典项大小的限制之谜

  • 对于 CHAR(10) 列,使用 utf8 字符集时,MySQL 会直接分配 10 * 3 = 30 字节的固定空间。
  • 如果将其改为 4 字节的字符集,内存和磁盘固定开销会变成 10 * 4 = 40 字节,索引键值也会变长,可能导致许多旧的数据库表空间与性能问题。
  • 为了保证兼容性和空间控制,MySQL 只能另起炉灶设计 utf8mb4

3. 深入技术细节:编码与存储对比

3.1 编码边界

标准 UTF-8 编码规则如下:

  • U+0000 ~ U+007F:1 字节
  • U+0080 ~ U+07FF:2 字节
  • U+0800 ~ U+FFFF:3 字节
  • U+10000 ~ U+10FFFF:4 字节

MySQL 的 utf8 仅支持到第 3 步。任何需要 4 字节编码的字符(如 Emoji、罕见汉字等),utf8 都无法识别。

3.2 假如强行插入非法字符会发生什么?

  • 严格模式下(sql_modeSTRICT_TRANS_TABLES:直接报错 Incorrect string value
  • 非严格模式下:数据会被静默截断或转换为问号 ?,造成永久数据丢失。

3.3 字符串长度比较

对于同一个字符串 "a😂b" (a、U+1F602、b):

  • CHAR_LENGTH() 返回 字符数:两者都返回 3。
  • LENGTH() 返回 字节数
    • utf8 列无法存储,因为 😂 需要 4 字节。
    • utf8mb4 列中,返回 6(a 占 1 字节,😂 占 4 字节,b 占 1 字节)。

4. 迁移实战:如何将现有数据库从 utf8 转为 utf8mb4

如果你发现自己还在用过时的 utf8,请按照以下步骤安全迁移(务必先在测试环境验证并在操作前备份数据)。

4.1 修改数据库、表、列的字符集

-- 1. 修改数据库默认字符集
ALTER DATABASE your_database_name CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;

-- 2. 修改表的默认字符集和校对规则
ALTER TABLE your_table_name CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- 3. 或者单独修改某一列的字符集
ALTER TABLE your_table_name MODIFY column_name VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

4.2 修复索引键长度超出问题

InnoDB 默认最大索引键长度为 767 字节(某些配置下为 3072 字节)。由于 utf8mb4 将每个字符的最大占用从 3 字节提升到 4 字节,原本的 VARCHAR(255) 索引可能变成 255 * 4 = 1020 字节,超过限制导致创建索引失败。

解决方案:

-- 创建索引时指定前缀长度
ALTER TABLE your_table ADD INDEX idx_column (column_name(191));

(191 * 4 = 764 字节,安全在 767 字节限制内。若开启了 innodb_large_prefix,最大可为 768,可适当调整)

4.3 修复应用程序连接参数

确保应用程序连接 MySQL 时,连接字符集也设置为 utf8mb4

  • JDBC 连接串:jdbc:mysql://host:port/db?useUnicode=true&characterEncoding=UTF-8 (实际 MySQL 驱动会自动映射到 utf8mb4,但显式将其当作 UTF-8 连接即可)
  • PHP PDO:SET NAMES utf8mb4;
  • Python 连接:charset='utf8mb4'

5. utf8mb4 的排序规则选择

避免乱码不仅靠字符集,还需搭配正确的排序规则(Collation)。常见推荐:

排序规则 特点 适用场景
utf8mb4_unicode_ci 基于 Unicode 标准算法,处理多语言准确 多国家语言混合的应用
utf8mb4_general_ci 较旧的默认规则,速度快但准确度低(部分语言排序不完美) 纯英文或性能要求极高的简单查询(不推荐新项目)
utf8mb4_0900_ai_ci MySQL 8.0 默认,基于 Unicode 9.0,区分重音和大小写不敏感 推荐使用,现代且最精确
utf8mb4_bin 按二进制值比较,区分大小写 需要精确匹配的场景,如 Base64 串、区分大小写的用户名

推荐新项目直接使用 utf8mb4_0900_ai_ci(MySQL 8.0+)。


6. 常见问题排查指南

Q1:我设置了 utf8mb4,可还是无法存储 Emoji? 检查以下三点:

  1. 列字符集真的改成了 utf8mb4 吗?用 SHOW CREATE TABLE your_table; 确认。
  2. 连接字符集是否正确?执行 SHOW VARIABLES LIKE 'character_set_%'; 确保 character_set_clientcharacter_set_connectioncharacter_set_results 都是 utf8mb4
  3. 在客户端执行 SET NAMES utf8mb4; 试试。

Q2:能在同一个表中混用 utf8utf8mb4 列吗? 可以,但不推荐。混用会导致查询时发生隐式字符集转换,可能影响索引使用并带来性能开销。

Q3:从 utf8 升级到 utf8mb4 后,存储空间会增加吗? 仅对于包含 4 字节字符的数据才会增加。如果你的数据全是 ASCII 或 BMP 字符,存储空间不会变化。


7. 终极建议:彻底告别 utf8

  • 所有新建数据库、表、列,一律显式使用 utf8mb4
  • 不再使用 utf8,它只是 utf8mb3 的别名,且官方已计划在未来版本中移除
  • 对于旧项目,制定迁移计划,利用批量转换脚本逐步改造。

牢记:MySQL 里的 utf8 是假的,utf8mb4 才是真正的 UTF-8。 你本该一早就使用它。