MySQL 字符集 utf8mb4 和 utf8 的区别
MySQL 字符集 utf8mb4 与 utf8 的区别:从踩坑到精通
在 MySQL 数据库中,utf8 和 utf8mb4 是两个容易让初学者困惑的字符集。表面上它们都声称支持 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_mode含STRICT_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?
检查以下三点:
- 列字符集真的改成了
utf8mb4吗?用SHOW CREATE TABLE your_table;确认。 - 连接字符集是否正确?执行
SHOW VARIABLES LIKE 'character_set_%';确保character_set_client、character_set_connection、character_set_results都是utf8mb4。 - 在客户端执行
SET NAMES utf8mb4;试试。
Q2:能在同一个表中混用 utf8 和 utf8mb4 列吗?
可以,但不推荐。混用会导致查询时发生隐式字符集转换,可能影响索引使用并带来性能开销。
Q3:从 utf8 升级到 utf8mb4 后,存储空间会增加吗?
仅对于包含 4 字节字符的数据才会增加。如果你的数据全是 ASCII 或 BMP 字符,存储空间不会变化。
7. 终极建议:彻底告别 utf8
- 所有新建数据库、表、列,一律显式使用
utf8mb4。 - 不再使用
utf8,它只是utf8mb3的别名,且官方已计划在未来版本中移除。 - 对于旧项目,制定迁移计划,利用批量转换脚本逐步改造。
牢记:MySQL 里的 utf8 是假的,utf8mb4 才是真正的 UTF-8。 你本该一早就使用它。