关于mysql的emoji编码问题
- 事情起因: 夺宝吧项目中我们从微信,qq,微博等第三方平台拉取用户名到本地db, 起初设置的db中表
user
fielduser_name
是utf8字段。但遇到了秀川和明霓的用户名奔跑的五花肉☔
,秀川
♑
于是大家讨论起emoji编码的问题。 - 他山之石:首席的同学说他们已经采过这个坑了,存emoji时有丢失,拿出来会乱码。于是他们用的base64对要存的byte进行编码然后入库,出库的时候先进行一次解码。然而感觉这样做虽然解决了问题但是存储层对应用层不透明了。
- 背景:感谢浩然的分享。 我们知道展示在屏幕上的字符是一个个unicode,而存储在计算机里的是一个个byte (更具体的说是utf8编码的byte)。
- 问题分析:
- 究竟是哪出现了乱码: 首先有可能是计算机不支持这个unicode字符。这样肯定是乱码, 然而,如果计算机本身不支持的话及时通过一层base64编码问题一样解决不了。其次可能是存储utf8的byte或者解析时出了问题。 这样说的过来
- 问题验证:google了一下mysql的
utf8
和utf8mb4
, 然后得到两者的区别utf8
只支持3个byte及以内,utf8mb4
支持4个byte的扩展字符。 (我也不知道一个unicode
编码成utf8最多多少byte google后得到最多4个byte). 于是下结论, utf8mb4对于存储emoji是完全够用的,as long as emoji是一个个的unicode
问题验证:
验证编码问题我一直觉得用python3比较合适
- step1:
user_name
character setutf8
db 配置:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'lucky_draw',
'USER': 'domob',
'PASSWORD': 'domob',
'HOST': '10.0.0.200',
'PORT': '3306',
'CONN_MAX_AGE': 25,
# 'OPTIONS': {'charset': 'utf8mb4'},
},
}
- 存一个3byte的emoji
u = User.objects.get(id=1)
u.user_name=b'\xE2\x93\x82'
u.save()
u = User.objects.get(id=1)
u.user_name
正常输出'Ⓜ'
- 存一个4byte的emoji
u.user_name=b'\xF0\x9F\x86\x9A'
u.save()
报错: Incorrect string value: '\xF0\x9F\x86\x9A' for column 'user_name'
step2:
user_name
character setutf8mb4
db 配置同上- 存一个4byte的emoji
u.user_name=b'\xF0\x9F\x86\x9A'
u.save()
报错: Incorrect string value: '\xF0\x9F\x86\x9A' for column 'user_name'
- step3: googel发现是db配置的问题 修改
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'lucky_draw',
'USER': 'domob',
'PASSWORD': 'domob',
'HOST': '10.0.0.200',
'PORT': '3306',
'CONN_MAX_AGE': 25,
'OPTIONS': {'charset': 'utf8mb4'},
},
}
重复上述测试,发现能完美的存取4byte的utf8
到此问题解决,通过go程序和mysql client同样得到验证。
结论:mysql默认支持的是精简的utf8字符集,猜想可能和分配存储有关系(比如char型的长度是n 如果是utf8集只需要分配3n个byte就好,如果是utf8mb4需要分配4n个存储)
在实际操作中,如果需要db支持存储emoji,首先需要field的collection设置能utf8mb4 同时在链接mysql的charset时要声明这种charset.