先说清楚,脚本是我写的,锅也是我的。写在这就当留个记录。
事情是这样。我们那个跑批系统,每天凌晨一点半开始,对账、出报表、给财务那边推数。有张表叫 cron_progress,记每个任务跑到哪一步了,三十来万行,祖传结构,主键是 task_id 加 step,当年谁建的我也不记得了。前阵子发现这张表里堆了几年前的历史记录,索引也乱,跑批开头那个状态检查越来越慢,我看它不顺眼很久了。
昨天白天写了个归档脚本,把三个月前的记录搬到 history 表,然后 DELETE 掉。本地测试库跑了两遍,没问题。晚上十一点多我又在测试库上确认了一遍,中间为了单独测归档那段的逻辑,我把 DELETE 后面那个 WHERE 条件注释掉了——测完忘了加回来。事情到这就埋下了。
今天凌晨,对账推完,两点四十,我脑子里想的是顺手把生产也清了,省得早上再操作。登上去,执行。
回车按下去,屏幕停了不到一秒,返回:受影响行数 412003。
我当时那个感觉,怎么说呢,就像骑车下坡发现刹车线断了。三十多万行,我第一反应是看 WHERE 在不在——不在。第二反应是看有没有备份。
先做的事是把 crontab 敲注释。跑批下面还有两个任务没跑,如果它接着跑,状态表空了,会当成新任务从头处理一遍,财务那边今天早上就能看到重复的推数。停掉之后我给主管发了条消息,四个字:出了点事。没细说。他也没回,估计睡着了。
然后是备份。那台机器每天凌晨一点整有一份全量 mysqldump,落在 /data/backup 下面,gzip 的,一个多 G。这意味着一点到两点四十之间一个多小时的增量没了,那段时间跑批还在写这张表。好在这机器的 binlog 一直开着,当年 DBA 说占地方想关,我拦了一句,说留着吧,万一。就这一句,今天值回票价。
恢复走的路数很土:先在备机起一个 5.7 的实例,跟生产同版本,把一点的 dump 灌进去。灌了差不多二十分钟,表回来了,四十万行上下,但不全,中间一个多小时的增量里面没有。
接下来是 binlog。我平时不碰这东西,position 那套玩法看着就头大,mysqlbin-log 那几个参数我老是记不住。翻文档翻了两分钟,说实话没看进去,人那时候是慌的,字都读不进。后来让它帮我理了一遍,从哪个 position 开始、到 DELETE 之前那条事务结束的 position 停,start-datetime 和 stop-datetime 怎么写。捋完我自己又核了一遍——这东西核还是要核的,它不会替你担责。
重放出来一个 sql 文件,一个多 G 的 binlog 我只截了一小段。导进去,select count(*),412003。跟我删之前那个数对上了(编辑框里还留着我随手打的 select,结果在上面挂着)。行数对上,又抽查了几个 task_id 的 step 顺序,也对。
把这张表 dump 出来,导回生产。三点五十七分。四点整,crontab 的注释去掉,手动跑一个最小的任务,状态正常往下走。然后我坐在那,把那段脚本里的 WHERE 加回去,注释删掉,提交。
早上财务那边什么事都没发生。主管回了我一句:嗯。就这样。
要我说,这事跟 AI 没多大关系,工具就是个工具,binlog 那玩意存在那么多年,当年我们也是这么恢复的,只不过那时候没东西帮你把参数理一遍,得自己去翻 O'Reilly 那本,翻到的时候人已经熬到五点了。所以它也就是打打下手,省了我一个钟头,别的没了。
不对,我得补一句。后来复盘的时候我想了想,真正救命的不是 binlog,是我那句"留着吧,万一"。当年拦那一下,纯属觉得删日志这事不踏实,也没想那么远。
行吧,说了这么多,核心其实就一句:写脚本的时候把 WHERE 条件注释掉这种毛病,趁早改了。我自己也是今天才改。
睡了。