MySQL 存储 emoji 表情

By | 2017年7月2日 | 阅读:585 次

前言
在公司的小程序项目中,获取微信用户的信息后,后台要存储用户的昵称、头像地址等信息,但是在存储带 emoji 表情的昵称时 MySQL 存储出现了 ? 的乱码,但是昵称为 ✨ 的用户并没有乱码。。虽然把 nick_name 从 utf8 改到 utf8mb4 即可正常存储(已存储的不会正常显示)解决了问题,但是觉得还是很诡异,现在就来仔细了解下为啥只会出现部分 emoji 乱码;

环境:mysql Distrib 5.7.17

1. 乱码问题

  • 1.1 诡异的乱码:✨ 能正常存储,但是 😘 存储就会乱码,update utf8字段的值为😘 ,在非截断模式下会直接插入报错:

  • 1.2 小插曲:Sequel Pro 和 DataGrip 两个工具对同一张表的乱码的显示值不一样…DG的第一反应是生疏昵称的中文乱码了,Jetbrains家的IDE不总是那么棒。。
    • Sequel Pro
    • DataGrip

2. 存储 emoji 乱码的原因及解决办法

2.1 乱码原因

Emoji 字符 在存储时要用到 4 个字节。在 5.5.3 之前的 MySQL utf8 字符集的 utf8_general_ci 最大支持 3 字节,只能存储部分Unicode值;在 5.5.3 之后新增了字符集 utf8mb4,最大支持 4 字节,且utf8mb4 完全兼容utf8 ,所以能正常存储 emoji;

  • MySQL 字节数对应能存储的 Unicode 字符范围

  • utf8:无法表示 U+010000 - U+10FFFF 范围的字符
    • Emoji 维基 知道,根据 Unicode 6.0标准: ✨ 的编码为 U+2729,在 3 个字节内可正常显示
    • 😘 的编码为 U+1F618,超出了 3 个字节,所以不能正常显示
    • 其实 emoji 表情也是慢慢加入 Unicode 的,早期加入的 ✨ 就在3字节以内,丰富的 emoji 后期才加入的,已经超出了 3 个字节能编码的范围,先前觉得乱码诡异是以为 emoji 的编码都是挨在一起的233,所以遇到问题应该多思考才是。
  • utf8mb4:最大的 emoji 编码是 U+1F9C0,在 4 字节表示范围内,能正常存储和显示。

2.2 解决办法

2.2.1 直接更改字段的字符集为 utf8mb4

2.2.2 使用 base64 编码存储昵称

如果大量昵称都有emoji,可以用 base_encode 后存储为 utf8 字符集,在 laravel 的 Model 中处理字段时再 decode 一次,输出显示即可;

同样的,在备份和导入 wx_users 表时,使用 mysqldump --default-charater-set=utf8mb4 即可。


3. 其他

segmentfault 的一个贴说 utf8 混合 utf8mb4 的坏处:如果视图在两个分别是 utf8 和 utf8mb4 的列上使用 join, 将会导致 mysql 的性能大幅度下降(隐式类型转换);所以在列上单独使用 utf8mb4 时还需考虑一堆性能问题,不是简单的存下来再说。。目前不会用到视图上,后期遇到性能优化时候再说咯233

发表评论

电子邮件地址不会被公开。