我先说结论:脚本没问题,问题在我。现在我改任何线上数据之前,第一件事都是先导备份——这习惯是那天晚上逼出来的。
站上有 140 多篇旧文章,有些是好几年前写的,文末的说明早过时了。我想统一给它们末尾补一段新的更新提示,省得一篇篇点进去改,就写了个脚本。
思路很土:走后台的编辑页,把每篇正文取出来,末尾拼上我那段话,再提交回去。逻辑就这么两行半,我大概半小时就写完了。
跑之前我顺手导了一份全量数据,把站上所有文章拉成一个 JSON,落在磁盘上。这是习惯动作,平时改东西前都这么干,当时也没多想。
脚本跑完,日志刷刷地过,每篇都是 [ok],提交返回 200。我扫了一眼,全绿,挺满意,关了电脑睡觉。
第二天中午,我翻后台想看看那几篇加得怎么样。点开第六篇的时候,人愣住了——正文没了。整篇文章只剩我刚加的那段提示,前面几千字,干干净净,一丁点不剩。
我手有点抖,赶紧往上翻。不是一篇。数了一下,十篇:12、185、186、187、188、189、190、197、198、208。
四篇技术文、几篇杂谈,还有一个下载页。全废了。
当时第一反应是数据库出事了,或者谁误删。查了一圈都不是,数据库好好的,就是这十篇的 text 字段被写成了「空 + 那段提示」。
回头去看脚本。取正文那一步,我用的是正则去匹配后台编辑框里的 textarea。大部分文章没问题,但有几类:正文里带图片附件的引用、有的夹了 HTML 结构、还有一篇正文里正好有跟我的正则打架的字符。这些情况下,正则没匹配到内容,取出来是空字符串。
而我的脚本,拿到空字符串,没停,也没报警——它老老实实把「空字符串 + 我那段提示」拼起来,当成新正文,提交了。
后台返回 200。日志打的是 [ok]。它从头到尾都在如实汇报:「我提交成功了」。它确实成功了,成功把我十篇文章清空了。
我盯着那段代码看了半天,就一个念头:报错其实还好,最怕的是这种东西——它觉得一切正常。
好在备份在。就是我头天晚上随手导的那份 JSON。脚本是十一点整开跑的,备份是十点三十七导的,中间差二十来分钟。就这二十来分钟,备份里的是干净正文;那晚要是偷懒没导,这会儿就是另一个故事了。
恢复的过程没什么技术含量,就是笨办法:从备份里把每篇原文取出来,逐个比对标题、字数,回写到后台,提交完再用接口把正文拉回来,一格一格数长度对不对。十篇,一篇一篇过。最后长度全对上了,心里那块石头才算落地。
事后复盘,这几条是拿真金白银换的:
一,动线上数据之前,先导全量备份,没得商量。而且别图省事只放 /tmp,机器一重启就没了,找个稳当地方存。
二,脚本取到的字段,提交之前必须验一下——是空的、或者不含预期内容,就绝不能覆盖。空值覆盖是好数据最隐蔽的杀手。
三,接口返回 200 不代表你做对了。成功只代表请求发过去了,不代表改的是你想要的。要看的永远是回读结果,不是返回码。
四,先拿一篇试。跑通了、前后对比过,再放开批量。那晚我就是觉得逻辑简单,直接一把梭了。
五,脚本要能重复跑。同一个操作跑两遍结果不能变,不然迟早越跑越乱。
我们这行,整天跟客户说数据要备份、操作要谨慎。轮到自己的站,一个「就这么两行」的脚本,一晚上就教会了我一遍。
现在那几个脚本还在,只是开头都加了同一件事:先把这份数据全量导出来,导不出来就不许往下走。教训这东西,落在代码里才记得住。
评论