无停机添加 CHECK 约束
本页目录
添加 CHECK 约束有益于:
- 提升数据质量
- 在 PG12+ 中,定义
NOT NULL约束不需要停机 (更多信息参考此处)
要在不中断服务的情况下添加 CHECK 约束,我们需要:
- 快速定义带有
NOT VALID标志的约束 - 在一个单独的事务中,"验证"现有行是否满足该约束
使用NOT VALID添加CHECK约束
示例:
alter table t
add constraint c_id_is_positive
check (id > 0) not valid;
这仅需要一个非常短暂的 AccessExclusiveLock,因此在负载较大的系统上,应设置较低的 lock_timeout 并进行重试 (参考:Zero-downtime Postgres schema migrations need this: lock_timeout and retries)。
🖋️ 重要:当 NOT VALID 约束添加之后,新的数据写入会立即进行检查 (而旧数据尚未验证,可能会违反约束):
nik=# insert into t select -1;
ERROR: new row for relation "t" violates check constraint "c_id_is_positive"
DETAIL: Failing row contains (-1).
验证
完成添加约束的过程后,我们需要验证旧数据:
alter table t
validate constraint c_id_is_positive;
该操作会扫描整个表,因此对于大表可能需要较长时间 — 但此查询仅获取表上的 ShareUpdateExclusiveLock,不会阻塞运行 DML 查询的会话。但是,如果有 autovacuum 在预防事务 ID 回卷的模式下运行并处理该表,或有其他会话在该表上创建索引或执行其他 ALTER 操作,则锁请求会被阻塞。因此,在运行 ALTER 之前,应确保没有这些繁重的操作在执行,以避免过长的等待时间。
我感
这个技巧很不错,在创建分区表的时候也可以使用这个技巧,直接 alter table xxx add constraint 会全程 8 级锁,可以先 not valid,再慢慢校验,最后 attach。
最后,遵循🔐的获取原则:
- 够用即可:使用满足条件的锁中最弱的锁模式
- 越快越好:如果可能,可以用(长时间的弱锁+短时间的强锁)替换长时间的强锁
- 递增获取:遵循2PL原则申请锁;越晚使用激进锁策略越好;在真正需要时再获取。
- 相同顺序:获取锁尽量以一致的顺序获取,从而减小死锁的几率