西西软件园多重安全检测下载网站、值得信赖的软件下载站!
软件
软件
文章
搜索

首页 → 业内动态 → 业内资讯 → MySQL存emoji报错1366:字符集改对了还差3处才对

MySQL存emoji报错1366:字符集改对了还差3处才对

相关软件相关文章发表评论 来源:本站整理时间:2026/11/3 11:03:34字体大小:A-A+

作者:xiix点击:0次评论:0次标签: MySQL字符集 utf8mb4 emoji存储 排序规则 1366报错

  • 类型:数据库类大小:136.4M语言:中文 评分:10.0
  • 标签:
立即下载

上周有个读者私信我,说他往自建 VPS 上的 MySQL 插一条带表情的评论,直接被数据库挡回来了,报错是 ERROR 1366 (HY000): Incorrect string value: '\xF0\x9F\x98\x80' for column 'content' at row 1。他说自己早就把库建成了 utf8mb4,网上查到的教程也都照着改了,为什么还是不行?我让他把连接时的几个变量值打出来一看,全明白了他只改了库这一层,表和列还是老的 utf8mb3,而且客户端连接用的还是 latin1。字符集这件事从来不是改一句就完事,它是一个从客户端到服务端、再到每一张表每一个字段的完整链路,只要有一环对不齐,emoji 就会在某一层被截断或者被转成问号。

如果你现在正打算换一台机器专门跑数据库或者做业务迁移,可以先看看 moonvm(低价 SSD 云服务器) 的配置和线路说明,SSD 对小文件随机读写和索引扫描的差别很实在;预算更充裕、又看重企业级机房和运维响应的,也可以对比一下 rabisu(高性能基础设施与云服务器) 这家的方案,先把机器底子打好,后面折腾字符集才不会白费功夫。

下面按我实际排查的顺序,把这条链路上容易踩的三个地方拆开讲清楚,每一处都给可以直接抄的检查命令。

MySQL 数据库服务器内部 SSD 固态硬盘与内存条特写,象征字符数据吞吐

一、1366 报错到底在说什么:先看清是哪一层拒的

很多朋友看到 1366 就直接去网上搜「MySQL 改 utf8mb4」,然后照抄一堆命令,改完发现没用。问题在于 1366 这个错误有几种长相,处理思路完全不同:

第一种,插入时直接报错。报错信息里带着一串 \xF0\x9F\x98\x80 这样的十六进制字节,这是最典型的症状。四个字节的 F0 9F 98 80 是 emoji 的 UTF-8 编码,F0 开头就意味着这个字符需要 4 个字节才能表示,而老的 utf8(也就是现在的 utf8mb3)每个字符最多只认 3 字节。服务端在往目标列里塞的时候发现塞不下,就直接抛 1366 拒收。这说明接收这一侧的列还是 3 字节的字符集。

第二种,插入成功但查出来是问号或者乱码。这种情况数据库没有报错,说明列本身够宽,问题出在链路的另一端:客户端连接时声明的字符集和实际传过来的字节对不上,MySQL 按 latin1 去理解这四个字节,再转成列的字符集存进去,取出来自然就是 ????。很多 emoji 表情在终端里显示成方块或问号,其实就是这条链路上的编码转换在来回折腾。

第三种,老数据里存的就已经是问号了。这种最麻烦,因为字符集改成什么也救不回来,问号是当初写进去时就丢了信息。所以正确顺序永远是先修链路,再转存量数据,顺序反了就是白干。

先跑这三条命令摸清现状,注意加 --default-character-set=utf8mb4 只是为了让 mysql 客户端自己的显示不乱,它不会改变服务端任何设置:

SHOW VARIABLES LIKE 'character_set_%';

SHOW VARIABLES LIKE 'collation_%';

SHOW CREATE TABLE your_table\G

第一条看的是连接层和服务层的默认字符集,第二条看的是默认排序规则,第三条看的是你真正在用的那张表在 CREATE 语句里被写成了什么。这三条输出贴在一起,基本就能定位到问题落在哪一层。

开发者工位上运行数据库管理工具排查字符集问题

二、第一处:库、表、列是三层,改一层不算改

这是最容易漏的一处,也是那位读者栽跟头的地方。MySQL 的字符集是可以继承的:库上设了 utf8mb4,新建的表如果没显式指定就跟着继承;表上设了 utf8mb4,新建的列同样继承。但反过来不成立你后来在库层面改了字符集,之前已经建好的表和列是不会跟着变的,它们在磁盘上的元数据里还记着老的 utf8mb3。这就好比你在小区门口换了告示牌,但楼里每一户门口贴的旧牌子还在。

用 SHOW CREATE TABLE 看一眼就知道。如果表定义里是 CHARSET=utf8mb3 或者压根没写(继承自一个老的库),那就得动手改:

ALTER DATABASE your_db CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

ALTER TABLE your_table CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

这两句的差别很关键。ALTER DATABASE 只改库层面的默认值,作用范围仅限于「以后新建的表」;ALTER TABLE ... CONVERT TO 才是真的把表里所有列的字符集、排序规则全部转换一遍,同时把表本身的默认字符集也改掉。如果你的表里有 VARCHAR、TEXT 这类字符型字段,必须用 CONVERT TO 才会生效。

顺便提一句,如果你的表有外键约束,CONVERT 会报错。MySQL 8 支持设置 SET FOREIGN_KEY_CHECKS=0; 关掉校验再改,改完记得打开。

三、第二处:排序规则要跟着一起定,别让它自己挑

字符集管的是「怎么存」,排序规则(collation)管的是「怎么比大小、怎么排序、去重时算不算同一个」。这两者是一对,指定字符集的时候顺手把排序规则也指定掉,才不会留下隐患。

常见的排序规则有两套:utf8mb4_general_ci 系列是 MySQL 5.7 时代的老默认,大小写不敏感,兼容性最好;utf8mb4_0900_ai_ci 系列是 MySQL 8.0 的新默认,对 Unicode 的处理更准确,emoji 也能正确参与排序和唯一索引。老库升到 8.0 之后,这两套规则混着用的现象非常普遍。

如果你的一张表里有 UNIQUE 索引,而且索引列里存了带表情的内容,排序规则不一致会直接引发 Duplicate entry 报错因为在 general_ci 眼里两个不同的表情可能算「相同」,而在 0900_ai_ci 眼里它们是不同的。想要统一,最省事的做法是先看清楚库里到底有几套规则在跑:

SELECT TABLE_SCHEMA, TABLE_NAME, TABLE_COLLATION FROM information_schema.TABLES WHERE TABLE_SCHEMA='your_db';

SELECT TABLE_NAME, COLUMN_NAME, CHARACTER_SET_NAME, COLLATION_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='your_db' AND CHARACTER_SET_NAME IS NOT NULL;

这两条查询基本是排查字符集问题的万能工具,建议直接背下来。

云端数据中心服务器阵列与网络线缆,象征分布式数据库字符编码处理

四、第三处:连接字符集是最后也最容易漏的一环

很多人在服务端把库表列全改成了 utf8mb4,结果本地开发机插 emoji 还是乱码;或者反过来,本地好好的,插到服务器上就坏。问题几乎都出在连接这一环。

MySQL 在建立连接时,客户端会带上自己的字符集,握手阶段协商出一个「连接字符集」。从 MySQL 8.0 开始,服务端的默认字符集改成了 utf8mb4,但如果你的连接代码显式指定了 character_set=latin1 或者用了很老的驱动默认行为,协商出来的结果就还是 3 字节。Java 里连接串写 jdbc:mysql://host/db?useUnicode=true&characterEncoding=utf8 是老写法,新版本驱动里应该明确写成 characterEncoding=utf8 并确认驱动版本;PHP 的 PDO 要检查 PDO::MYSQL_ATTR_INIT_COMMAND;Python 的 PyMySQL 默认就是 utf8mb4,一般不用动。

验证办法很直接,在客户端里执行:

SHOW SESSION VARIABLES LIKE 'character_set%';

SHOW SESSION VARIABLES LIKE 'collation%';

注意是 SESSION 不是 GLOBAL。前面那几条查的是服务端默认值,这里查的是「你当前这条连接实际在用什么」。如果这里的 character_set_client、character_set_connection、character_set_results 里有一个还是 latin1 或者 utf8mb3,那就是它了。

这三个变量可以用 SET NAMES utf8mb4; 在会话级别临时改掉,用来验证判断对不对。验证通过后再去改代码或者连接池配置顺便说一句,连接池里的连接是复用的,改连接池的初始化参数比改业务代码靠谱得多,不然改一次漏一处。

五、改完之后:存量数据怎么处理

链路修好之后,还有一个遗留问题:以前用 3 字节字符集存进去的 emoji,早就变成了问号或者被截断的半个字符。这部分数据是救不回来的,唯一的办法是找到源头重新灌一遍。如果只是乱码但不是 emoji 丢失,转换完字符集之后通常就正常了。

另外提醒一句,ALTER TABLE ... CONVERT TO 在 MySQL 5.7 和 8.0 上都会重建整张表,数据量大的时候相当于复制一遍数据,耗时可能以小时计,而且期间会有锁。这不是能随便在生产库上敲的命令。稳妥的做法是:用 pt-online-schema-change 或者 gh-ost 这类在线改表工具,让变更以「影子表 + 增量同步」的方式跑;或者干脆新建一张 utf8mb4 的表,用 INSERT ... SELECT 把数据搬过去再切换。数据量小的话,直接选业务低峰期做 ALTER 完全没问题,别过度设计就行。

六、收尾检查清单

按这个顺序过一遍,基本能覆盖 95% 的字符集问题:

一、看库:SHOW VARIABLES LIKE 'character_set_database'; 确认库是 utf8mb4,排序规则是不是你想要的。

二、看表和列:SHOW CREATE TABLE 看表定义,再用 information_schema 的 COLUMNS 查询确认每一列的字符集,别漏了那种建表时没指定、默默继承了老字符集的列。

三、看连接:SHOW SESSION VARIABLES 查客户端、连接、结果三个变量是否都是 utf8mb4。

四、看驱动:改的是连接串还是连接池的初始化参数,改完记得重启应用,验证一下新的连接确实是新参数。

五、实测:用一个真表情(不是那种在某些字体下显示成方块的符号)插进去,再 select 出来肉眼确认,必要时用 HEX(content) 看字节是不是完整的四字节序列。

最后提醒一句关于 emoji 本身的技术细节:MySQL 里 utf8mb4 支持的是 Unicode 的完整范围,包括绝大多数 emoji,但带变体选择符(VS16)和 ZWJ 组合序列的那些「复合 emoji」(比如带肤色或者职业标识的组合),在某些老版本客户端的字体和显示环境下可能看起来怪,但存储层面是没问题的,存进去再取出来字节完全一致。如果你的业务强依赖这些组合表情,务必在自己真实的客户端环境里做一轮完整的往返测试,别只在命令行里试一次就放心。

字符集这东西,说白了就是一条链路上每一环都要说同一种「语言」。你把客户端、服务端、库、表、列这五处对齐了,emoji 自然就存得进去、也取得出来。整个过程里最费时间的往往不是改配置,而是定位「到底哪一环没对齐」,上面这几条查询能帮你省下大半时间。

    相关评论

    阅读本文后您有什么感想? 已有人给出评价!

    • 8 喜欢喜欢
    • 3 顶顶
    • 1 难过难过
    • 5 囧囧
    • 3 围观围观
    • 2 无聊无聊

    发表评论

    昵称:
    表情: 高兴 可 汗 我不要 害羞 好 下下下 送花 屎 亲亲