零停机迁移在旧代码运行时就改表结构
零停机迁移是兼容性与可用性目标,不是把语句变短之后自动获得的保证。数据变化时,新旧应用可能同时运行,因此需要一起安排表结构、写入行为、回填、读取切换以及恢复方式。删除信息并不会因为前面采用了增量步骤就变得可逆;是否还能回退,取决于旧程序需要的数据是否仍然存在且正确。 取锁等待、行级冲突、资源消耗和应用假设都重要。只改元数据的操作也可能被别的事务挡住;回填没有持有排他表锁,也可能耗尽磁盘吞吐或复制容量。验收不能只测一条语句有多快,还需要观察并发业务、队列等待与恢复条件。 先扩展,再回填,后收缩 扩展阶段引入旧读写方能够容忍的新表示。先部署兼容写入方,再依赖新数据;回填存量行并验证不变量后,才切换读取。收缩必须等旧二进制、后台任务和回退需求都不再依赖旧结构。增加可空列经常有用,但仍需取锁和兼容性检查,不能把任何增加列操作都宣布为绝对安全。 双写必须定义失败与先后顺序。同一数据库事务中更新两列,可以维持一致性;分开提交则可能只成功一半。仍然只更新旧列的老写入方,会让已经回填的新列再次过时。因此需要先退役这些写入方,或提供经过验证的同步机制,再宣布已经收敛。回滚只有在它所依赖的旧表示仍然准确时才安全,不是打开双写就自动保证不丢数据。 回填应采用可重启的小批次和稳定进度键。需要防止旧快照覆盖并发应用刚写入的新值,例如事务内检查版本号,或使用符合业务不变量的条件更新。仅检查新列为空并非通用答案,它是否足够取决于写入行为。跳过的行和并发变化的行还要复查,写入方收敛之后再次验证。分批缩短单次事务,却可能增加总工作量,更不能替代正确的发布顺序。 锁是其中一道失败边界 PostgreSQL 的 ACCESS EXCLUSIVE 会与普通读写冲突。许多 ALTER TABLE 形式即使不用重写数据,也要获取它。普通 UPDATE 则获取 ROW EXCLUSIVE 和行锁,不会因此把所有其他写入方都全局排除。具体冲突取决于命中的行和并发操作;长事务会一直持有相关锁,直到事务结束,而不是语句返回后就全部释放。 排队中的强锁可能继续延迟后来的冲突请求。应识别实际阻塞关系、事务边界与等待事件,不能仅凭哪个事务最老就认定它是原因。SQLite 的回滚日志与 WAL 模式属于不同引擎契约。旧文中的毫秒和页数对比,在本次审阅中没有建立精确基准证据;它们不能当作 PostgreSQL 预测,也不是本轮测量结果。 在较弱锁下验证 以 PostgreSQL 17 为例,只有当前写入方已经满足条件时,才添加合适的 CHECK ... NOT VALID。这个选项延后检查旧行,却立即约束新的插入和更新。随后 VALIDATE CONSTRAINT 用 SHARE UPDATE EXCLUSIVE 扫描,它允许普通数据读写,但会与部分维护和结构操作冲突。因此验证并非完全不取锁,也不是对所有并发活动都没有影响。 ...