凌晨两点,我盯着那条ALTER TABLE的执行结果,手心全是汗。
不是 SQL 写错了——SQL 本身干干净净,加一列,再补一个默认值。错在我是在生产库上,手动、直接、用 Navicat 连上去跑的。跑完那一下,线上订单下单的接口就开始超时,监控面板上一片飘红。我到现在都记得旁边同事那个眼神,就差没开口问我"你怎么敢的"。
那晚的事故,查来查去才发现根因跟我加的列本身没关系,是被我一直压着的另一个查询把锁给憋住了。但那一刻我真的慌了——因为我不知道自己刚才"手动改生产库"这个动作,到底动过哪张表,改了什么,有没有人记录过。整个公司没有一份表结构变更的记录,全靠大家心里有数。
打岔一下,你别笑。后来我翻 git 历史想找有没有人做过表结构备份,结果一个都没找到。那一刻我算是彻底想明白了,数据库的变更,是整条发布链路里最容易被"人肉"的一环,也是我最看走眼的一环。
我为什么当时不直接用它?说实话,那阵子团队里确实没人提"版本管理"这四个字。大家默认:表结构嘛,谁手头有事谁就顺手改一下,改完记住就行。可"记住"这回事,在三个人以上协作时就是玄学——你记你加过这个字段,我记我删过那张表,等到要联调,两边都对不上,然后一起骂环境有问题。骂完一查,是自己早改过生产库没通知别人。
你可能会想,那我用个现成的迁移工具(Migration)不就完了?比如 Flyway、Liquibase 之类,把每个变更写成一个带版本号的脚本,按顺序执行,谁改过一查就知道。我当时也是这么想的。可一上手才发觉,最大的坑根本不是工具,是你想不想把"改库"这件事,从"个人操作"变成"受控变更"。工具再多,你不愿意把每一次ALTER都写成脚本、走一次评审、留一份记录,它就还是裸奔。
我说个我后来踩过的真实坑。为了省事,我把表结构变更和业务代码放在同一个发布脚本里,想着一条流水线全搞定。结果有一次代码先发上去了,脚本里的迁移因为锁超时没跑完,生产库的表还是旧结构,新代码一访问就报"列不存在"。那半小时的线上故障,全程是我一个人在那一边安抚客服,一边补跑迁移。后来我才悟到,迁移最好跟代码拆开:代码可以回滚,Schema 迁移通常没法干净地"回滚",把它混在一起,等于把两个不同节奏的东西强行捆死,出事了连谁先谁后都说不清。
还有更现实的一层——改了库之后,你怎么知道它到底行不行。我们常说的那个词,叫"生产环境的表结构漂移":迁移脚本在测试环境跑得好好的,到生产一跑就报错,因为生产的表和测试的早就长得不一样了。所以现在我会在每次迁移跑完后,先做一次结构比对,确认目标环境真的和我预期一致,再让业务代码上去。这一步看着啰嗦,但能省掉太多"我以为它改了"的尴尬。
这段经历也让我回头重新看了一遍自己的分工。你发现没有,越是到了关键的生产库,越没人敢轻举妄动,可恰恰又最容易因为"怕麻烦"而走野路子。数据库的变更管理,考验的不是你会不会写ALTER TABLE,而是你愿不愿意把每一步都变成可以被追溯、被评审、被回看的流程。写代码是能力,敢不敢把代码库之外的东西也当代码来管,才是更值钱的那点本事。
说到这我得老实承认,Agent(智能体)也确实帮我省了不少事——我可以让它先读一遍现有的迁移脚本清单,标出哪些环境还没同步,再让它生成一份结构比对报告。但它再怎么帮,前提还是我得先把"迁移有版本、变更留痕"这套框架立起来。工具和 Agent 都是放大器,放大器不会替你定规矩。
话说到这,把问题抛给你吧:你现在的表结构变更,是靠流程管着,还是靠"记住"?有没有哪次改库,是你事后怎么也想不起来当时改了什么?评论区聊聊,我特别想听你"手动改生产库"的那次翻车经历——毕竟这种事,说出来比埋在心里强。