给在线表重命名字段时,直接执行 ALTER TABLE ... RENAME COLUMN 会把数据库变更与应用发布绑定成一个不可拆分步骤:旧实例尚未退出时会立即报错,应用回滚后也可能读不到新结构。一个可复现的替代方案是把迁移拆成扩展、切换、收缩三个阶段。
假设要把 users.nickname 改为 display_name。第一阶段只做向后兼容的扩展:
alter table users add column display_name text;
create index concurrently users_display_name_idx
on users (display_name)
where display_name is not null;
随后发布兼容版本:写入时同时更新两列,读取仍以旧列为准。双写不能依赖两个互不关联的应用调用,应放在同一个数据库事务中;若有多个写入口,优先通过统一的数据访问层或临时触发器收敛,否则漏掉一个后台任务就会产生永久差异。
回填使用小批次并允许重复执行,避免长事务和整表锁:
with batch as (
select id
from users
where display_name is null
order by id
limit 1000
for update skip locked
)
update users u
set display_name = u.nickname
from batch b
where u.id = b.id
and u.display_name is null;
每批提交后记录进度,并监控未回填行数、两列不一致数和写入错误。回填完成不能立刻删除旧列,先把读取切到新列,同时继续双写;观察窗口内若需回滚应用,旧版本仍可依赖 nickname。确认所有运行实例和离线任务都已使用新列后,停止双写并进入收缩阶段:先移除旧列相关代码,最后在独立发布窗口删除旧列。
建议把发布门禁写成可执行检查:
select count(*) as missing
from users
where display_name is null and nickname is not null;
select count(*) as inconsistent
from users
where display_name is distinct from nickname;
只有两项都为零、旧字段读取量归零且兼容版本已覆盖全部写入口,才允许切换读取。删除旧列属于不可通过应用回滚恢复的动作,应要求单独审批和备份验证,不能与读路径切换放在同一次发布。
可复现验收包括:新旧两个应用版本并存时都能读写;回填任务并发运行不会重复覆盖新值;回填中断后重启能继续;切换读取后回滚应用仍可工作;存在任一不一致记录时收缩步骤必须被门禁拒绝。这个流程增加了发布次数,但把高风险的瞬时结构切换变成了每一步都可观测、可暂停的兼容迁移。