必须用utf8mb4字符集,因mysql的utf8实为utf8mb3,仅支持3字节编码,而emoji和部分生僻汉字需4字节;若连接层、表、列、客户端任一环节未统一设为utf8mb4,就会报错incorrect string value或出现乱码。

必须用 utf8mb4 字符集,且连接层、表、列、客户端三者全部对齐,否则哪怕只差一层,\xF0\x9F\x98\x80(emoji)或生僻汉字就会变成乱码或被截断。
为什么 utf8 不行,而必须是 utf8mb4
MySQL 的 utf8 实际是 utf8mb3,最多只支持 3 字节 Unicode 字符;但 emoji 和部分汉字(如「?」「?」)需要 4 字节编码。一旦遇到这类字符,MySQL 会直接报错:Incorrect string value: '\xF0\xA8\x92\x82' for column ...,或静默丢弃后续内容。
-
utf8mb4是 MySQL 5.5.3+ 引入的真正 UTF-8 实现,兼容全部 Unicode 字符 -
utf8mb4_unicode_ci排序规则比utf8mb4_general_ci更准,推荐用于多语言场景 - 仅改表或列的字符集不够——
character_set_client、character_set_connection、character_set_results也得是utf8mb4
SET NAMES utf8mb4 必须在每次连接后执行
即使 JDBC URL 或 my.cnf 里写了 characterEncoding=UTF-8,老版本驱动(mysql-connector-java )仍可能 fallback 到 <code>utf8mb3。最保险的做法是在建连后立刻执行:
SET NAMES utf8mb4;
这等价于同时设置:
SET character_set_client = utf8mb4SET character_set_connection = utf8mb4SET character_set_results = utf8mb4
漏掉任意一个,都可能导致插入时正常、查询时乱码,或反过来。
字段定义必须显式指定 CHARACTER SET utf8mb4
不能只依赖数据库默认字符集——很多 MySQL 实例的 default_character_set 仍是 utf8(即 utf8mb3)。建表时必须逐列声明:
CREATE TABLE users ( id INT PRIMARY KEY, nickname VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, bio TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci );
注意:
-
TEXT、VARCHAR、ENUM等所有字符串类型都要单独加CHARACTER SET,不能只写在表级 -
ALTER TABLE ... CONVERT TO CHARACTER SET utf8mb4只改表和列定义,不改已有数据的编码;需配合ALTER TABLE ... MODIFY ... CHARACTER SET utf8mb4才能重编码存量数据 - 如果字段存的是二进制语义内容(比如 base64 编码的图片描述),用
BLOB更安全,避免字符集转换干扰
应用层插入时别手动拼 SQL 字符串
哪怕字符集全对了,直接拼接 'O'Reilly' 这种带单引号的字符串,仍会触发 ERROR 1064。根本原因是 SQL 解析器在进入存储过程或执行前就失败了。
- 永远优先用参数化查询(
PreparedStatement/mysqli::prepare/PDO::prepare) - 如果必须动态拼接(比如构建 WHERE 条件),用
mysql_real_escape_string(已废弃)或现代等效函数(如PDO::quote) - 对 JSON 字段,用
JSON_VALID()校验后再存,避免因非法字符导致整个字段写入失败
最常被忽略的一点:换行符 \n 和制表符 \t 在 TEXT 字段里能存,但若客户端连接未设 SET NAMES utf8mb4,它们可能被转成空格或丢失——这不是数据问题,是传输层编码错位。











