长事务导致脏数据回写

Posted by rain on January 25, 2024

案发背景

小G找到我说,发生怪事,他说在表里改了某个字段(master_cluster_status),改完当场是成功的改完之后过了几分钟又变回去,重复了几次都没用,最终还是变回 failed,看update_time,现象是过几分钟更新,感觉是机器动作,不是人为。

用户在我们这里搭建的容灾,我们调用华为api实现,同时也会定时去华为云拉取特定状态回写到自己表

处理过程

于是我很好奇,写程序那么多年,虽然出现过奇怪的事情,但从没灵异案件出现,肯定是bug~~

我让小G找到对应ORM对象(java和mysql绑定的对象)查看master_cluster_status在哪些方法使用过,重点观察定时任务。分析约莫半个小时,确认所有入口,都不会把master_cluster_status设置成failed 搜索failed这个枚举的引用,也排除是程序里有逻辑主动设置

继续分析

思来想去,这时想起之前有处理过长事务问题,事务结束才把数据写入表里,于是我猜测应该是以下这种可能情况

Snipaste_2024-01-25_20-09-31.png

也就是说,很可能小G每次更新操作,都是定时任务启动了,等定时任务结束,定时任务回写

小伙伴好奇,没地方修改这个值,为啥会把failed写入呢,原因就出在前同事这个更新逻辑里,做了全字段更新,也就是定时任务启动查出来的failed又覆盖写回这个表。

于是,我让小G先临时把master_cluster_status改对,再这个id更改成其他值,这样根据id做update时报错,后面再改回原id,观察了几个轮回,update时间有变,不会再刷回脏数据

定时任务为啥会这么久

查看了表里数据,容灾任务大概80个,发现调用华为云api,部分站点会需要几秒(例如北方的乌兰察布区域)整个过程算下来跑完需要5分钟以上

又上去看了这个定时任务的配置,发现之前同事配置3分钟一次,把它调整到8分钟(临时调,观察是否有其他副作用)

故事结束

定时任务有长事务,可能导致脏数据回写,可采用乐观锁加版本号解决。同时使用框架调用时,尽量不硬编码,未修改的字段不要覆盖set回原表