跳到主要内容

上周四晚上,我差点把一张表搞坏

敏姐转开发
敏姐转开发

· 阅读约 3 分钟

先交代背景(我知道我又要交代太多了,但不说清楚后面没法讲)。我们组有个历史遗留的数据问题:某条业务线的记录,状态字段有几批是脏的,一直挂着没人修。我接的那个小模块刚好要读这个状态,读出来是错的,所以这两个月我顺手把这块脏数据也算进自己模块里在处理。上个月我写了个修复脚本,一直在测试环境跑,没上过真环境。

上周四 leader 说可以修了,让我挑个低峰期做。我挑了晚上八点。

脚本本身很简单,一条 UPDATE,按 id 分批,每批 500 行,跑完 sleep 一下,免得把库压着。要动的行我心里有数,提前查过,符合条件的两百多行(这个数字我记的很清楚,因为写在复盘里了)。跑之前我按做测试的习惯做了一件事:把要动的行先导到一张临时表里。就是这条习惯把后面的事救了。

八点二十左右,第一批跑完,返回的 affected rows 是 31874。

我盯着那个数字看了可能有一分钟。我以为看错了,刷新,重查,还是 31874。

问题出在 WHERE 上。我只按状态筛了,忘了加业务线的限定条件。这个状态值在另一条业务线上也是合法的,那条线上一共三万多行记录停在这个状态。也就是说,我一条 UPDATE,把别人家三万行的状态全改了。

这里我可能想复杂了,但当时的判断过程是这样:先确认脚本停了没有(停了,我设了失败即退),再确认能不能回滚。回滚不了,脚本里是分批 commit 的,autocommit 也开着。所以我手上只剩一样东西——那张临时表。

然后更麻烦的是那张临时表。我导的时候只选了三个字段,id、status、update_time,因为当时觉得够用了。(我当时觉得。这四个字现在看着想打自己。)业务的其他字段我没动过,所以其实也不需要,但 update_time 的问题就躲不掉了:我要回写 status,就得先判断哪些行在我改完之后,又被正常业务改过。那会儿虽然是低峰,还是有零星的写入。

从九点做到凌晨一点,我把这三万多行按 id 拉出来,跟临时表里的原值对齐,再按"原值 / 我改成什么 / 现在是什么"分成几类。大部分是简单的,现在还是我改错的那个值,直接回写。有一小批麻烦,是它在我改完之后被正常流程又流转了一次——这类有十一行,我一行一行看的,最后决定不动,因为回写反而会把一个合法的流转结果打回去。做完之后我又仔细的核对了一遍行数。

第二天我跟 leader 说了。他没说我什么,只问了一句"数据对完了吧"。我说对完了,附了一张对账表。然后我主动写了个复盘,虽然当天没出事。

这件事之后我给脚本加了两样东西:一个 dry run 参数,跑的时候只打印影响行数不执行;还有一个是强制带业务线条件,不传就报错退出。第二个其实是拍脑袋加的,不知道对不对,但至少下次不会再有"忘了加限定条件"这种事。

对了,那 31874 行我全都查过一遍,一条没漏。这是唯一让我稍微好受一点的地方。

先这样吧,回写那部分我还想再包一层校验,等弄完再说。

敏姐转开发
敏姐转开发

测试八年,转开发第一年。慢一点没关系,返工最贵。

查看主页 →