Emoji 和数据库之间的坑,你踩过没?

swift 发布于 2022年12月04日

Emoji 就是我们常说的表情符号,如果你的产品是经常处理 UGC 内容的话,就要特别注意它的处理,这次就和大家分享一个我实际遇到过的一个问题。

起因

发现这个问题的起因,是之前的一个微信小程序产品,有一段时间经常有用户反馈无法登录。 可是自己尝试复现这个问题很多次,都没有成功。起初呢,没太当回事,也是因为其他开发工作实在太多了,有点忙不过来。

但是,反馈类似问题的用户一直不断,在某一天我想着专门抽出时间来处理这个问题。 还是按照以前的方法,用我自己的微信账号,在小程序各种边界条件下调用后端的登录接口。还是一样的结果,依然没有复现出问题。

每次陷入这种死局时,我一般会去干点别的事,喝杯咖啡,出去走了走。 果然,这个办法还是管用,回来时候我换了一个思路。

既然我自己的场景无法复现问题,我就从这些用户的信息分析开始。 小程序的反馈系统是能看到用户的 Open ID 的。 从这个 Open ID 自然就能追踪到后端数据库具体的记录。

查看这个用户对应的数据记录,用户名确实没有正确的写入,那么我就尝试直接在 MySQL 会话中直接把他的用户名设置进来:

结果是在我的预期之外,数据库竟然报错了:

ERROR 1366 (22007): Incorrect string value: '\xF0\x9F\x92\x98\xE8\x98...' for column `test`.`ugc_user_info`.`nick_name` at row 1

我继续用同样的方式直接用 SQL 设置我自己账户的名字,结果却是成功设置。 然后我把之前反馈无法登录的用户名都对比了一下,发现了真正的问题。 这些无法登录的用户名都有一个共性,就是用户名中都带有 Emoji 符号。

如何解决

找到问题根源后,经过了一番搜索,也找到了解决方案。导致这个错误的原因是 MySQL 使用了 utf8 字符集。 简单来说 MySQL utf8 字符集只支持 1-3 字节长度的数据,而 Emoji 恰好是 4 字节长度,导致了编码不匹配,造成数据库报错。

解决方法也很简单, 以 MySQL 为例,将原有的数据库和表使用的字符集改成 utf8mb4 就可以了。 如果你使用的不是 MySQL 就按照你当前使用的数据库实际情况来处理。

utf8mb4 - 4字节 UTF-8 编码, 比早期的 utf8 编码多一个字节,用来表示更多的字符。 在 MySQL 的语境下,实际上 utf8 是 utf8mb3 的一个别名,这样的名称对比,你就会觉得 utf8mb4 这个名字不那么奇怪了。

关于字符集准确的定义, MySQL 官网有明确的说明:

utf8mb4 - https://dev.mysql.com/doc/refman/8.0/en/charset-unicode-utf8mb4.html

utf8mb3 - https://dev.mysql.com/doc/refman/8.0/en/charset-unicode-utf8mb3.html

感兴趣的伙伴可以自行去查看官方文档。

那么简单说一下如何将我们之前用 utf8 创建的数据库,转换成 utf8mb4。

首先在库级别和表级别修改相应的字符集:

-- 修改数据库字符集
ALTER DATABASE db_name CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 修改表字符集
ALTER TABLE `table_name` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

然后还需要修改数据库设置 my.inf, 有些文章这样设置:

[client-server]
default-character-set=utf8mb4

在我自己的实际情况,用 default-character-set 会在重启数据库的时候报错, 还有一种方式是通过 loose-default-character-set 来设置:

[client-server]
loose-default-character-set=utf8mb4

实在没有时间深究这个设置的问题了,总之, 这两处改动完成之后,重启数据库服务,Emoji 就可以正常使用了。 我们也可以通过 MySQL 看到当前数据库的字符集:

mysql> SHOW VARIABLES WHERE Variable_name LIKE 'character%' OR Variable_name LIKE 'collation%';
+--------------------------+--------------------+

| Variable_name | Value |
+--------------------------+--------------------+

| character_set_client | utf8mb4 |
| character_set_connection | utf8mb4 |
| character_set_database | utf8mb4 |
| character_set_filesystem | binary |
| character_set_results | utf8mb4 |
| character_set_server | utf8mb4 |
| character_set_system | utf8 |
| collation_connection | utf8mb4_unicode_ci |
| collation_
database | utf8mb4_unicode_ci |
| collation_server | utf8mb4_unicode_ci |
+--------------------------+--------------------+

rows in set (0.00 sec)
-----------------------------------

如果这样还不行的话,某些情况下还需要对表中单独的 Column 进行设置

ALTER TABLE table_name CHANGE column_name column_name VARCHAR(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

基本上这几步操作做完之后, Emoji 就可以正常写入数据库了。

写在最后

总的来说,如果你的后端使用 MySQL 数据库。我们新创建的应用,都应该使用 utf8mb4 字符集。 以前的 3 字节 utf8 更适用于早年 Web 桌面互联网的时代,那时候基本不会使用 Emoji 这些超字符集。 但现在的互联网用户,这类新字符的输入占比是非常高的,特别是 UGC 类型的应用。 即使你的后端数据库用的不是 MySQL,我相信也会存在类似的情况。

通过这个问题,我们基本上可以建立一个意识,就是 Emoji 使用的字符集是需要特别注意的,在其他内容都能正常保存的情况下, Emoji 是有可能因为你存储字符集的问题,出现错误的。

并且现在用户使用 Emoji 的频率并不低,比如我这个出现登录问题的小程序,就有10%以上的用户名中含有 Emoji,这种问题还并不好调试,如果不是因为用户登录这个关键入口,可能都很难发现问题。

如果大家有什么相关的经验,也欢迎在留言区补充。


如果你觉得这篇文章有帮助,还可以关注微信公众号 swift-cafe,会有更多我的原创内容分享给你~

本站文章均为原创内容,如需转载请注明出处,谢谢。
关注微信公众号
发现更多精彩
swift-cafe