变更一个数据库字段需要几步?
小时候都被问过一个脑筋急转弯,把大象放进冰箱有几个步骤?我们一开始都会抓耳挠腮,去想着该如何把大象塞进冰箱。最终揭晓的答案却根本不关心具体的操作方法,只是提供了 3 个步骤组成的流程,「把冰箱打开,把大象放进去,再把冰箱关上」。而对于每一位开发者来说,变更数据库字段是绕不过去的操作。而当被问及需要几步时,不少人都会脱口而出 1 步, 不就是执行一条 ALTER TABLE 语句嘛。
这当然不是一道脑筋急转弯题,但确实是一道经典的技术面试题,而答 1 步的同学,基本就挂掉了。实际上,一个标准的数据库字段变更操作需要分成很多步,比如给字段重命名,会分成 6 步:
创建一个使用新名字的字段
更新应用,同时双写 (dual-write) 旧字段和新字段
把启动双写前,旧字段的数据回填 (backfill) 到新字段
当回填结束后,添加诸如 NOT NULL 之类的约束到新字段
更新应用,移除所有对于旧字段的依赖,只使用新字段
删除旧名字的字段
两年多前,Bytebase 的诞生就是来专门解决这个业界难题,两年多过去了,Bytebase 已经形成了一套全面的解决方案,包括:
可视化变更
批量变更
大表在线变更
库表同步
SQL 审核
GitOps
代码 CI/CD 流水线集成
Schema 漂移检测
敏感变更脚本内容脱敏
Xata 基于 Postgres schema 实现的可回滚变更
接下来我们会写一系列的文章来拆解一下数据库变更的步骤,并且提供每一个环节的最佳实践。而这第一篇就从数据库变更的三种流程说起。
和应用打包在一起的优点是简单,代码只要针对最新的 schema 写就行了,因为启动顺序保证了只有数据库变更到了最新的 schema,才会启动新的应用版本。但这个方案也有不少局限性:
不能支持应用副本和数据库多对一的情况,否则在升级过程中,就很难协调到底由哪个副本来变更数据库,无法保证新旧应用版本和数据库 schema 的兼容性。
回滚困难,因为变更完后,新版本就直接写到新的数据库结构了。这个时候如果发现升级有问题,回滚的话就要把数据库和应用一起回滚,这可能会造成数据丢失。而如果只是回滚应用的话,则又要考虑旧应用版本和新数据库结构的兼容性问题。
如果数据库变更需要比较长的时间,而应用本身不允许长时间的不可用,那么也不能用该方案。
if (version >= 2.0)
// use v2 schema
else
// use v1 schema
这个方案解决了第一种方案的几个问题:
可以支持应用副本和数据库多对一的情况,因为数据库的变更是一个单独的流程,不再和应用升级绑定了。
减少回滚难度,因为数据库变更完后,如果发现问题,就直接回滚数据库的变更。因为这时新的数据库结构还没有实际使用,所以没有数据库丢失的问题。而应用运行的还是老版本代码,所以只要把数据库回滚到老版本,就也自然没有了兼容性问题。