← 返回题目列表

数据库表结构变更如何设计才能兼容灰度发布?

中等 第 28 / 33 题 更新于 2026/07/30
表结构变更灰度发布兼容设计

简化版

表结构变更要遵循向前兼容和分阶段发布:先加可空字段或新表,代码双写或兼容读,再回填数据,确认稳定后切读,最后清理旧字段。不要在一次发布里同时删除字段、改语义和上线新代码。

详细版

灰度发布期间,新旧代码可能同时运行。如果数据库变更只适配新代码,旧代码可能报错;如果直接删除旧字段,新代码没全量上线前也可能失败。

安全流程通常是 expand-migrate-contract:先扩展 schema,不破坏旧代码;再迁移和回填数据;最后在所有代码都切换后收缩旧结构。

典型例子是把 full_name 拆成 first_namelast_name。不能直接删 full_name,要先加新字段、双写、回填、切读,再删除。

完整版教学

一、为什么表结构变更会影响发布

线上发布不是瞬间完成的。灰度、滚动重启、多实例部署意味着一段时间内新旧代码共存。数据库却通常是共享的,一次 DDL 会影响所有版本代码。

如果新代码需要字段 nickname,但字段还没加,就会报错;如果先删除旧字段 name,旧代码还在读它,也会报错。

所以 schema 变更的核心是兼容窗口,而不是单纯执行 DDL。

二、expand-migrate-contract 是什么

这个模式分三步:扩展、迁移、收缩。

Expand:新增结构,保持旧逻辑可用
Migrate:双写、回填、切读
Contract:确认无旧依赖后删除旧结构

它的本质是把高风险的一次性大改,拆成多个可回滚的小步骤。

三、字段拆分例子怎么做

假设要把 full_name 拆成 first_namelast_name

第一步加新字段,允许为空;第二步新代码写入时同时写旧字段和新字段;第三步后台回填历史数据;第四步读逻辑切到新字段,并保留旧字段兜底;第五步确认没有旧代码后删除旧字段。

阶段数据库代码
1加新字段旧代码不受影响
2新旧字段共存双写
3回填历史兼容读
4保留旧字段切新读
5删除旧字段清理旧逻辑

四、回填数据要可控

历史数据回填不能一次性 update 全表。大表更新会带来锁、redo/binlog、主从延迟和业务抖动。

可以按主键范围分批,例如每批 1000 行,低峰执行,记录进度,失败后可从断点继续。

UPDATE user_profile
SET first_name = ..., last_name = ...
WHERE id BETWEEN ? AND ?
  AND first_name IS NULL;

条件里加 first_name IS NULL 是为了让回填幂等,重复执行也不会覆盖新写入。

五、删除字段是最后一步

很多事故来自“字段没人用了吧,删掉”。实际上报表、脚本、异步任务、老版本服务可能仍在依赖旧字段。

删除前要看代码搜索、SQL 日志、慢查询、BI 报表、数据同步任务和回滚计划。最好先下线读写,再观察一段时间,再真正删除。

数据库清理应该像拆桥:确认没有车、设置绕行、观察流量,再拆。

六、常见误区与追问

  • 误区:DDL 执行成功就代表变更完成。 数据回填、代码切换、旧依赖清理才是完整变更。
  • 误区:灰度发布只需要代码兼容。 数据库是共享资源,schema 也必须兼容新旧代码。
  • 误区:字段删除可以和新代码一起发。 删除要放最后,确认没有旧依赖后再做。
  • 追问:大表回填如何避免影响线上? 分批、限速、断点续跑、低峰执行,并监控主从延迟。
  • 追问:如何支持回滚? 保留旧字段和双写一段时间,回滚代码仍能读到旧结构。

七、加强记忆

记忆钩子:表结构变更像修路,先拓宽路,再引流,再拆旧路;不能半夜把旧桥炸了再通知司机改道。

回答这题抓住“新旧代码共存”这个根因,再讲 expand-migrate-contract、双写、回填、切读、清理。它是数据库设计和发布工程的交叉考点。