utf8mb4 是什么?MySQL 中文网站和 Emoji 支持应该怎样配置
图片说明:示意图 图片来源:Public Domain(公有领域)
在支持中文、日文、生僻字以及 Emoji 的网站中,字符集配置看起来只是数据库初始化时的一行参数,实际上却会贯穿数据库、数据表、字段、程序连接和网页输出整个链路。任何一个环节仍然使用不合适的字符集,都可能出现中文乱码、Emoji 无法保存、查询排序异常,甚至在数据迁移过程中发生内容损失。
对于新项目来说,MySQL 官方推荐使用 utf8mb4。需要特别注意的是,MySQL 里的 utf8 并不等同于完整的 UTF-8,它历史上对应的是 utf8mb3,只能表示 Unicode 基本多文种平面中的字符,无法覆盖需要四字节 UTF-8 表示的补充字符。MySQL 8.0 已将 utf8mb3 标记为弃用,并建议新应用使用 utf8mb4。
UTF8MB4到底解决了什么问题
utf8mb4 是 MySQL 对 UTF-8 编码的完整实现,可以使用一到四个字节表示 Unicode 字符。普通英文字符通常只需要一个字节,许多中文字符需要三个字节,而部分中文、日文、韩文字符以及 Emoji 等补充字符可能需要四个字节。
这也是为什么一些网站能够正常保存中文,却在用户输入 Emoji 后出现 Incorrect string value。如果数据库字段仍然采用 utf8mb3,它无法表示四字节字符。
因此,新网站通常没有必要为了所谓的“节省几个字节”而主动采用 utf8mb3。MySQL 已经明确建议新应用使用 utf8mb4,而 utf8mb3 本身也处于弃用状态。
这里还有一个经常出现的 PHP 错误。mb_strlen() 并不是按照字节计算长度;它会根据指定的字符编码计算字符数,多字节字符通常按一个字符计算。例如:
$text = "中国😊";
echo strlen($text);
echo mb_strlen($text, 'UTF-8');
strlen() 返回的是字符串的字节长度,而 mb_strlen() 返回的是字符长度。因此,不能说“UTF8MB4 下必须使用 mb_convert_encoding() 才能统计字符数”。如果只是统计 UTF-8 字符数量,通常直接使用 mb_strlen($text, 'UTF-8') 即可。
如果需要按照用户感知的“字素簇”统计字符,例如某些 Emoji 由多个 Unicode code point 组合而成,则还需要考虑 grapheme_strlen() 等更高层次的处理方式。
数据库应该怎样配置
创建新数据库时,可以明确指定 utf8mb4:
CREATE DATABASE mydb
DEFAULT CHARACTER SET utf8mb4
COLLATE utf8mb4_0900_ai_ci;
如果使用 MySQL 8.0,这个排序规则就是 utf8mb4 的默认排序规则。MySQL 8.0 默认服务器字符集已经是 utf8mb4,默认排序规则是 utf8mb4_0900_ai_ci。
如果网站需要兼容较老的 MySQL 版本,也可以根据实际环境使用:
utf8mb4_unicode_ci
因此不能简单地说 utf8mb4_unicode_ci 在所有环境中都是唯一的“推荐方案”。对于 MySQL 8.0 新项目,应首先了解 utf8mb4_0900_ai_ci;如果需要兼容旧版本或已有数据库体系,则可以继续使用 utf8mb4_unicode_ci。
可以通过下面的命令检查数据库设置:
SHOW CREATE DATABASE mydb;
也可以检查服务器默认配置:
SHOW VARIABLES LIKE 'character_set%';
SHOW VARIABLES LIKE 'collation%';
需要注意,服务器默认值并不会自动修改已经存在的数据库、表和字段。MySQL 8.0 从旧版本升级时,也不会自动把已有数据库对象全部转换成新的字符集。
表和字段应该怎样设置
创建表时,可以把 utf8mb4 设置为表级默认字符集:
CREATE TABLE comments (
id INT PRIMARY KEY AUTO_INCREMENT,
content TEXT
) ENGINE=InnoDB
DEFAULT CHARACTER SET utf8mb4
COLLATE utf8mb4_0900_ai_ci;
这里有一个重要概念:TEXT 并不会因为数据类型本身而默认使用 latin1。如果字段没有单独指定字符集和排序规则,它通常继承所在表的字符集和排序规则。MySQL 对列字符集的选择存在明确的继承规则。
如果确实需要单独指定字段,也可以写成:
content TEXT
CHARACTER SET utf8mb4
COLLATE utf8mb4_0900_ai_ci
但对于一个整体采用 utf8mb4 的表,没有必要给每一个文本字段重复写一遍。
InnoDB 通常是现代 MySQL 网站的默认选择,它支持事务、外键以及 InnoDB FULLTEXT 等功能。不过,字符集和存储引擎是两个不同的问题,不能因为网站需要 Emoji 就认为必须使用某个特定存储引擎。
PHP连接MySQL时不要忽略连接字符集
数据库和字段配置正确,并不代表程序连接就一定正确。
PDO 可以直接在 DSN 中指定:
$dsn = "mysql:host=localhost;dbname=mydb;charset=utf8mb4";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
];
$pdo = new PDO($dsn, 'user', 'password', $options);
连接字符集的意义在于告诉 MySQL 客户端和服务器之间应该怎样解释传输的数据。MySQL 对字符串字面量和连接字符集也有明确的处理规则,因此连接层不能被忽略。
如果网站出现:
Incorrect string value
不要只检查数据库本身。应该沿着整个数据链检查:
数据库默认字符集 → 表字符集 → 字段字符集 → MySQL连接字符集 → PHP字符串编码 → HTTP响应 → HTML页面。
其中任何一层存在不一致,都可能造成问题。
Emoji并不需要特殊的数据库“Emoji模式”
只要数据库、字段和连接真正使用 utf8mb4,Emoji 就属于正常的 Unicode 字符,并不存在一个额外的“Emoji 开关”。
可以直接进行测试:
INSERT INTO comments (content)
VALUES ('中文测试 😊 🚗 🌎');
SELECT content
FROM comments;
如果能够正确保存和读取,说明数据库这一层已经能够处理这些字符。
网页端则应该声明 UTF-8:
HTTP响应也可以明确指定:
header('Content-Type: text/html; charset=UTF-8');
UTF-8 和 utf-8 在字符集名称表示上不存在所谓“旧浏览器必须使用大写 UTF-8”的规则,大小写并不是 Emoji 能否显示的关键。真正重要的是页面实际编码、HTTP响应声明以及浏览器对数据的正确解码。
另外,不能简单说 htmlspecialchars() 会把 Emoji “转换成 Unicode 编码”。htmlspecialchars() 主要用于 HTML 特殊字符转义,例如 <、>、&、引号等。正常的 Emoji 并不会因为调用这个函数就自动变成某种 Unicode 转义形式。
例如:
echo htmlspecialchars($content, ENT_QUOTES, 'UTF-8');
在把用户输入输出到 HTML 页面时,这样做通常仍然是必要的。它解决的是 HTML 注入和特殊字符转义问题,而不是 Emoji 编码问题。
已有数据库从旧字符集迁移怎么办
如果已经存在 latin1、utf8mb3 等旧表,不能简单执行一句命令就认为所有历史数据都安全完成转换。
例如:
ALTER TABLE comments
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_0900_ai_ci;
这个命令可以让 MySQL 转换表及相关字符列,但如果原数据本身已经发生乱码、经过错误编码转换或者存在无法正确映射的内容,改变字符集并不能自动修复历史损坏的数据。MySQL 官方也明确提醒,字符集转换过程中如果字符集之间无法兼容,可能发生数据损失。
因此生产环境迁移之前,应当先进行完整备份,并在测试数据库验证:
SELECT id, content
FROM comments
LIMIT 100;
同时抽查中文、生僻字、Emoji、多语言内容以及历史特殊符号。
对于大型生产表,也不能简单断言“CONVERT TO CHARACTER SET 一定会锁表”然后给出一个固定停机方案。具体影响取决于 MySQL 版本、表结构、DDL算法、索引以及运行环境。大表迁移应该按照实际版本和业务负载选择合适的在线 DDL、分阶段迁移或者专门的数据迁移方案。
utf8mb4会不会让数据库明显变慢
字符集选择确实会影响字符串存储、索引大小以及部分排序和比较操作,但不能简单得出“用了 utf8mb4_unicode_ci 就应该避免 LIKE”这样的结论。
LIKE 是否能够有效利用索引,首先取决于查询模式、索引结构、选择性以及优化器的执行计划。例如:
WHERE title LIKE 'mysql%'
与:
WHERE title LIKE '%mysql%'
完全是两种不同的查询场景。
真正需要关注的是 EXPLAIN 或 EXPLAIN ANALYZE 的执行计划,而不是因为使用了 utf8mb4 就禁止 LIKE。
同样,文本字段是否应该建立索引,也应该结合查询需求和字段长度决定。对于 VARCHAR、TEXT 等不同类型,索引设计需要考虑索引长度、查询方式和数据库版本。
utf8mb4和VARCHAR、TEXT应该怎样选择
utf8mb4 并不意味着所有文本都必须使用 TEXT。
例如标题、用户名、标签等长度明确的字段,通常可以使用合适长度的 VARCHAR:
title VARCHAR(255)
CHARACTER SET utf8mb4
文章正文、评论正文等长度可能较大的内容,则可以根据实际需求使用 TEXT、MEDIUMTEXT 等类型。
需要注意的是,VARCHAR(255) 中的 255 是字符长度概念,而实际占用空间还受到字符编码以及存储格式影响。使用 utf8mb4 后,单个字符最多可能占用四个字节,因此设计索引时不能只看字符数量。
对于现代 MySQL 应用,也没有必要为了节省空间而主动把某些普通文本字段改成已经进入弃用状态的 utf8mb3。MySQL 官方已经建议新应用使用 utf8mb4。
字符集不是网站安全的全部
正确使用 utf8mb4 可以减少字符编码错误,但它本身并不能解决 SQL 注入、XSS 或其他安全问题。
数据库操作仍然应该使用参数化查询:
$stmt = $pdo->prepare(
"INSERT INTO comments (content) VALUES (:content)"
);
$stmt->execute([
':content' => $content
]);
网页输出时,则应该根据输出上下文进行正确的 HTML 转义。
此外,数据库账户也不应该让网站程序长期使用 root。生产环境应使用权限范围尽可能小的专用数据库账户。
字符编码解决的是“数据怎样正确表示和传输”,输入验证、参数化查询、输出编码和权限控制解决的是另外一组安全问题。把这些概念混在一起,反而容易造成错误的安全假设。
一个实用的UTF8MB4配置原则
对于新的中文网站,如果使用现代 MySQL 8.0,可以把配置思路简化成几个层次。
数据库使用:
CHARACTER SET utf8mb4
排序规则根据 MySQL 版本和业务兼容性选择,例如 MySQL 8.0 可以使用:
utf8mb4_0900_ai_ci
PHP PDO连接明确指定:
charset=utf8mb4
HTML页面声明:
用户输入写入数据库使用预处理语句,输出到 HTML 时按照 HTML 上下文进行转义。
如果是已有系统,则不要看到 utf8 三个字就直接执行大规模转换。首先确认实际字符集:
SHOW CREATE DATABASE mydb;
SHOW CREATE TABLE comments;
SHOW FULL COLUMNS FROM comments;
再检查连接:
SHOW VARIABLES LIKE 'character_set%';
SHOW VARIABLES LIKE 'collation%';
最后抽样检查真实数据。
UTF8MB4真正重要的地方,不是“把数据库改成四字节”这么简单,而是让数据库、字段、连接、程序和网页对同一套 Unicode 数据形成完整的数据链。对于新项目,直接采用 utf8mb4 通常是最省事的方案;对于老系统,则应该先确认数据现状,再规划迁移。这样既可以正常处理中文和 Emoji,也能避免因为字符集误配置而留下乱码、数据损失和索引设计问题。
喜欢这篇报道?
使用下面的功能,方便以后继续阅读和分享 MNewsTV
关于文章收藏
收藏不需要注册帐号,收藏信息仅保存在当前浏览器中。
删除收藏请进入「我的收藏」进行管理。
捐助(Paypal): https://www.paypal.me/observeccp 订阅中国观察电报 Telegram : https://t.me/s/ObserveCCP