PostgreSQL 安全迁移与零停机 Schema 变更
使用 expand-and-contract、lock_timeout、Squawk、Testcontainers 和 pgTAP 降低 PostgreSQL DDL 锁表、回填与跨版本升级风险
零停机不是某条 DDL 的属性,而是旧应用与新应用能否在迁移窗口内同时工作。安全迁移需要兼容阶段、锁预算、真实数据规模测试、可观察执行和明确回退点。
默认采用 expand-and-contract
以重命名或替换一个高流量字段为例:
- Expand:添加新字段或新表,不删除旧结构;尽量使用短元数据操作。
- Dual compatible:应用能读取新旧结构,并在必要时双写;写入必须幂等。
- Backfill:按主键范围或时间窗口小批回填,限制事务时长、WAL 与 replica lag。
- Switch:先切读路径,再停止旧写入;用业务指标和校验查询确认。
- Contract:经过一个可回退发布窗口后,才删除旧结构。
直接把“加列、回填、设 NOT NULL、删旧列”放进一个长事务,通常会扩大锁、WAL、rollback 和复制延迟风险。
给锁等待设上限
BEGIN;
SET LOCAL lock_timeout = '2s';
SET LOCAL statement_timeout = '15min';
ALTER TABLE app.orders
ADD COLUMN IF NOT EXISTS fulfillment_state text;
COMMIT;示例超时不是通用默认值。lock_timeout 防止迁移长时间排队后在不可控时刻获得强锁;statement_timeout 限制执行时间。失败后应退出并调查 blocker,而不是无限重试。
添加约束时可把扫描与短锁阶段拆开:
ALTER TABLE app.orders
ADD CONSTRAINT orders_total_nonnegative
CHECK (total_cents >= 0) NOT VALID;
ALTER TABLE app.orders
VALIDATE CONSTRAINT orders_total_nonnegative;先在目标 PostgreSQL 版本和代表性数据上检查具体 DDL 的锁级别。CREATE INDEX CONCURRENTLY 也会消耗 I/O、WAL 和更长时间,并需要检查失败后留下的 invalid index。
推荐 CI 流水线
Schema / reviewed SQL
↓
Migration generation
↓
Squawk static checks
↓
Disposable PostgreSQL 18.4
↓
Apply every migration from an empty and upgraded state
↓
pgTAP + application integration + RLS negative tests
↓
PostgreSQL 19 Beta compatibility lane
↓
Representative-data rehearsal → staging → production- Squawk 检查常见危险迁移,例如非并发索引、未使用
NOT VALID的约束和一些锁风险;它不是零停机证明。 - Testcontainers for Node.js 在 CI 中启动真实 PostgreSQL,适合验证事务、锁、RLS、JSONB、扩展与驱动行为。
- pgTAP 在数据库内部测试函数、触发器、约束和策略。
如果使用 Drizzle ORM,可让 Drizzle Kit 生成普通 schema 变更,再对生成 SQL 进行 Squawk 与人工审核。复杂 index、policy、function、extension 和 PostgreSQL 19 新语法可以使用受审查的原生 SQL;ORM 无法表达不代表数据库不应该使用。
测试升级路径,而不只测试空库
CI 至少需要两种数据库状态:
| 起点 | 能发现的问题 |
|---|---|
| 空数据库执行全部 migration | 顺序、依赖、语法和初始化问题 |
| 生产版本 schema/脱敏数据执行增量 migration | 锁、回填、旧数据、约束与性能问题 |
再将测试拆成两条版本通道:
- 生产门禁:当前生产 major/minor,例如 PostgreSQL 18.4,失败时阻止发布;
- 前瞻兼容:PostgreSQL 19 Beta 2,可允许失败但必须归类、跟踪和在 GA 前清零。
不要让 Beta 测试替代稳定版本门禁。版本状态见 PostgreSQL 19 专题。
RLS 与安全对象必须做反向测试
迁移成功不代表权限正确。为每个 tenant 和 role 验证:允许的 SELECT/INSERT/UPDATE/DELETE 成功,不允许的跨租户读写失败;应用运行角色不是表 owner,必要时启用 FORCE ROW LEVEL SECURITY。详见 安全基线。
回滚应用不等于回滚数据库
删除列、不可逆回填、类型收窄和外部副作用可能无法安全逆转。每次发布应写明最后可回退时点、旧应用能否读取新 schema,以及 forward fix 的触发条件。
何时采用更重的工具
| 工具 | 适用场景 | 采用前先验证 |
|---|---|---|
| pgroll | 高频、兼容窗口明确的零停机 schema change | 支持的 DDL、代理/连接方式、回滚语义 |
| Bytebase | 多团队审批、SQL review、环境和审计治理 | 权限边界、部署模型、现有 CI 集成 |
| Database Lab Engine | 大型数据库的快速 clone 与迁移演练 | 存储、脱敏、clone 生命周期与成本 |
小团队先把 expand-and-contract、真实 PostgreSQL 测试、锁观察与恢复演练做好,再引入控制面。
Last updated on