本文发自 http://www.binss.me/blog/error-when-mysql-meet-emoji/,转载请注明出处。

近日,朋友告诉我binsite评论有bug,当提交的评论中带有emoji时,就会直接报500错误。interesting,我决定花一点时间来fix。

分析

我尝试只评论一个emoji表情,果然报500,于是调出了错误日志进行查看,发现错误出现在写入数据库时:

    ......
  File "/usr/local/lib/python2.7/dist-packages/MySQLdb/connections.py", line 36, in defaulterrorhandler
    raise errorclass, errorvalue
OperationalError: (1366, "Incorrect string value: '\\xF0\\x9F\\x91\\x8D' for column 'caption' at row 1")

字面上的意思是mysql认为'\xF0\x9F\x91\x8D'这个字符串不合法,因此无法插入。那'\xF0\x9F\x91\x8D'和之前的emoji有什么对应关系呢?

我查阅了emoji和mysql的一些资料。

Emoji

一直以来,我对emoji停留于在微信上发送?,对于其实现的一直缺乏了解。

资料显示,如今我们使用的Emoji实际上是一种已被标准化的,在Unicode位于U+1F300-U+1F64F区段的字符。不同的厂商对同一个emoji的Unicode字符有不同的表示,我们最熟悉的当然是Browser(浏览器),Apple(IOS),Google(Android)上使用的emoji。

根据查询表,U+1F300-U+1F64F区段需要用4个字节来表示。比如U+1F300,它的UTF-8表示为f0 9f 8c 80,因此需要用4 byte进行存储。

MySQL

当年mysql没有预料到会有Emoji这个“奇葩”出现,因此将utf8编码设置为3个字节。当遇到4个字节的字符时就懵了,显然放不下嘛,于是抛出了上述的错误。

查阅了相关资料,解决方法非常简单:MYSQL 5.5.3以后支持utf8mb4字符集,能够存放4个字节的字符。utf8mb4是utf8的超集,能够完美地向下兼容utf8字符串。当存入一个普通字符时占用 3 个字节,当存入一个Emoji 表情时占用 4 个字节。因此切换字符集即可。

解决

  1. 将整个db的默认字符集改为utf8mb4

    ALTER DATABASE binsite DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
  2. 把对应table的所有字符列(CHAR,VARCHAR,TEXT)改为utf8mb4

    ALTER TABLE comments CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
  3. 修改Django的settings.py,在相应的数据库连接上加上一项

    'OPTIONS': {'charset': 'utf8mb4'}
  4. 利用Emoji进行测试,成功支持Emoji

总结

  1. 涨姿势了
  2. 以后博客可以加Emoji来提升逼格了?