int导致的充值事故报告

原创:mysql01/19/2020发布pv:0uv:0ip:0twitter #mysql

原文地址:https://www.douyacun.com/article/8bec13a79bd42adda19f20120caf26fe

今天是2020年1月16号,记录这周发生的一件充值事故,1月12号周一下班后被通知某家代理商出现消耗金额超过充值金额800多万,当时第一反应是计费又出问题了,要帮忙定位问题,在家连zoom,翻看整个代理商从开户到现在每个月的充值数据,然后查询代理商每个月月月初的账户数据快照(实际每天晚上23:59都会快照一份存在hive中),一直到了晚上12点半,一直没有定位到问题出现的原因,然后领导就下线睡觉了,这个问题也出现一段时间不急于这一天了。

1月17号,大家都在吐槽年底了,为啥比平时还忙。针对昨天的bug,领导去支持了,我这边手上有两个大的功能点需要迭代上线。晚上回家后,被通知问题出现平台充值这边,不是计费的问题,头大,通过充值数据和消耗数据对比发现11月之前的正常的,12月开始出现消费大于充值金额;问题出现在11月份,还是完全没有思路,因为还是没有证据确定是充值问题还是计费问题,后续还是在翻看一直到12点半。

1月18号,下午定了一个会议室,专门来查这件事情,从代理商充值开始到12月1号00:00充值金额 - 12月1号00:00代理商下所有广告主的消耗金额 - 12月1号00:00代理商剩余金额 - 12月1号00:00代理商下广告主剩余金额。发现差值有:8486863.50元,这个时候可以错略确定的是充值问题,计费不会出现这么大的问题,而且19年1月份计费没有出现过重大bug。翻看代理商的充值记录有一条:8525163.53, 数值相近。不会是巧合,而且充值一般都是整数,问题出现了。

记得当时代理商是要预充值3000万,而数据库充值记录的金额字段是用的int(11), 单位:分,最多能存2147483647,所以当时后来又给代理商补了8525163.53,按道理这会应该it

也没有问题,问题出现账户表上,充值记录金额是有符号的,而账户余额是unsigned无符号的, 最大值到4294967295,是能存下3000万的。虽然充值记录显示2147483647,但实际账户确实是充值了3000万。

充值是有事务的,为啥没有回滚?如何避免这种情况的发生

MySQL默认情况下,当一个数据超出定义列的数据类型的范围时,数据会以数据类型所充许的最大值存储。不会出现报错,事务没有回滚。

在这种情况下,我们希望在插入或更新字段时,如果插入或更新的值超出定义的字段数据类型的范围时,提出报错,禁示修改数据,通过修改全局变量sq_mode STRICT_ALL_TABLES 实现

[SQL] insert into t6(`num`) value(2147483648);
[Err] 1264 - Out of range value for column 'num' at row 1

充值没有监控吗?为啥会出现这么重大bug

功能版本迭代,开始建表是int,后续有种改不动的感觉。大家都开始回避这个棘手问题。直至出现这样的问题,所以监控是非常必要的,代理商自助充值的功能也是在出现问题增加的监控,这是第二次出现充值问题。

总结, 经历两次充值事故后了以下几点:

  • 充值类型:big int, decimal,其他类型就不要考虑了,留坑而已
  • 充值sql_mod的session连接,一定要设置成为 STRICT_ALL_TABLES
  • 充值记录要完善,充值记录,账户充值后快照记录,多写一份数据备份记录,意味着可以更快的定位问题
  • 监控,对充值逻辑的反向验证,这个根据具体业务实现逻辑来。走社会主义特色道路。
  • 数据快照,公司重要数据一定要做每日快照,像这次如果没有代理商账户的每日快照数据备份,即便定位到了问题,也没有证据充分证明这件事,追账打官司复杂多就高了。

最后这两点不管是用在哪里都合适,就是因为用在哪里都合适所以很难重视起来,只有吃过亏的才知道。