记一次数据库更改字符集踩的坑

最近接手了一个闹钟App的后端项目,其中有个功能就是用户可以将本地闹钟同步到服务器,以便更换设备后能从服务器同步数据到新设备。但是这几天观察后台日志发现,同步的时候总是ERROR,定位错误发现跟数据库的字符集有关,在此记录一下本次线上错误的排查过程。

一、定位ERROR

通过Kibana查看最近的ERROR日志,可发现如下的错误描述。


   很明显可以发现,是因为insert的时候插入的数据不符合该列的要求。进而查看插入的数据,定位到插入内容含有特殊字符,比如emoji表情和其他一些字符。查询table的字符集发现这张表是utf8字符集,并不是utf8mb4的字符集,所以报这个错也是很正常了。看到这里你可能会问,这个问题应该在很久之前就应该发现了,为什么这几天才把它揪出来呢?这个项目很早了,包括后端和客户端,经过客户端同学的代码筛查,发现由于历史的原因,客户端对同步的结果的处理存在bug,导致后台同步失败某冲情况下也会提示同步成功,所以前台并不会暴露这个问题。
   所以当时建表使用的utf8字符集已经不能满足用户的自定义名称的需求,需要变更这张表的字符集为utf8mb4。但是一查数据,这张表就有七千多万的数据,更改字符集的话对之前的内容是否有影响,要执行多久?问题找到了,就开始找dba同学商量,最后决定先创建一张utf8mb4的新表,将原来的数据导入到新表,完成后,再将原表rename成新表,完成切换。说干就干,晚上提了工单,dba开始执行。

二、黎明前的黑暗

   第二天早上起床,发现接到了n多条服务报警,正是昨天更改字符集的服务。到了公司立马查看服务日志,发现服务挂了,重新部署启动,没有两分钟又挂了。这是什么原因呢?没有更改任何代码,只更改了一个表的字符集,整个服务就崩了吗?这是什么道理,一时间也是很懵。这时候dba那边发来了一长串slow sql,是一个两个表的连表查询,一开始我并未用explain分析sql语句,因为sql较简单,发现语句的书写还算规范,该走的索引也会走,一时间也无法定位问题。
   难道是昨天修改字符集之后,insert无异常,表的增量变大,查询变得更加慢了?根据这个猜测,为了临时解决这个问题,准备将这一小段代码修改成分表查询,在内存里聚合处理。正在准备拉分支修改代码的时候,dba同学那边发消息说是索引失效了。我赶紧explain了一下刚刚的sql语句,果然,之前应该走的索引在改了字符集之后就失效了。
修改字符集之后的explain结果:


修改字符集之前的explain结果:


原来连表查询的时候,两个表的字符集不同会导致索引失效,所以在这个如此多数据的表中进行连表查询是一件灾难性的事。
找到原因后,将连表查询的另一个表的字符集同样做出修改,之后索引正常,服务也正常了。

三、总结

通过这次处理线上问题,深深体会到在表数据很多的时候进行连表查询是一件多么可怕的事情,一不小心就把数据库或者服务搞崩溃了。还有一点很关键,不要像我一样看到sql简单就理所当然地以为会按照理论的情况走索引,拿到有问题的sql,一定要仔细分析,使用工具,比如explain来辅助分析sql,这样才不会遗漏问题。后续我们会对这个项目进行重构,将所有连表查询改成单表查询,然后在内存中对数据做聚合。另外这个表的数据已经到达七千多万了,修改了正确的字符集之后表的增量会更大,分表的工作也迫在眉睫。
本文仅此记录下问题排查经过,如果大家也遇到了类似的问题,希望可以进行参考。