4年前的代码,在没有任何改动的情况下,突然就炸了。
一、问题出现与处理
- 有用户反馈,自己的数据有问题,触发条件时积分没有增加。
- 一开始以为是用户操作不正确导致的,线上也没什么告警消息,但以防万一,自己打开APP进行了尝试。
- 自己尝试后发现真的有问题,就联系了同事,然后打开电脑查看相关代码。这是四年前就编写的代码,这段时间没有人改动过,最近没有代码上线,也没有改动过缓存和数据库配置。
- 积分信息保存在一张统计表,使用insert on duplicate key update语句进行新增、修改。
- 这段时间所有的积分都添加到了同一个用户上。
- 结合4、5,猜想是不是表的自增id出了问题,一看,发现该表主键id的AUTO_INCREMENT已经达到最大值。
- 当下的解决方式是将id由int类型改为bigint,表中实际只有两千万数据,大概十几分钟可以改完。
- 因为代码中id字段是声明为int类型,直接将数据库的id改为bigint的话,程序会报错,所以需要先改代码,上线后才能改表。
- 代码顺利上线,改表成功后,通过流水表的记录将用户积分修复为正确数据。
二、关于主键id自增
对于insert、insert on duplicate key update、insert ignore语句,如果没有指定主键id字段值,就算没有插入记录,也会导致AUTO_INCREMENT递增。
那么,自增字段达到了最大值时,MySQL是怎么处理的呢?
对于普通二级索引的自增字段,MySQL不会报错,而是始终将自增字段设置为最大值。
对于主键、唯一索引的自增字段:
- 如果是insert语句,会报唯一键冲突的错误。
- 如果是insert on duplicate key update,所有insert on duplicate key update语句的update操作都会执行到id为该字段数据类型的最大值的记录上。
- 如果是insert ignore,会返回rows affected = 0。
三、总结
- 开发时使用insert on duplicate key update或insert ignore时,需要考虑这条语句的执行频率高不高,每天可能会增长多少id。
- 如果预估的id增长速度很快,业务场景又允许的情况,可以考虑使用先select,再决定是insert还是update的方式。
- 如果预估的id增长速度很快,业务场景又一定要使用insert on duplicate key update或insert ignore时,可以把id字段类型设置为bigint,几乎不用担心id不够用。
- 了解MySQL的SQL执行逻辑,有时候还是挺有帮助的,例如这次线上问题,因为之前了解过主键id自增会产生的问题,所以在看到代码中的SQL再结合线上问题的现象,能够比较快的联想到主键id的问题,从开始排查问题到给出解决方案,大概用了10分多钟。
网友评论