PostgreSQL CHECK NOT VALID 创建成功,VALIDATE CONSTRAINT 却失败
NOT VALID跳过旧行验证,后续VALIDATE仍会检查历史数据。按真实CHECK表达式找条件为假的行,保留NULL语义和新写入保护。
给旧表加 CHECK (...) NOT VALID 已成功,随后执行 VALIDATE CONSTRAINT,却收到 check constraint "..." is violated by some row。创建成功只说明约束已经登记,不能据此判断历史数据满足条件。
本文按 PostgreSQL 18 说明普通业务表的行级 CHECK。NOT VALID 跳过添加时对旧行的验证,后续验证会检查这些数据;它不会自动修正旧值,也不是暂时关闭全部检查。
找到准确的约束定义
在报错数据库中读取实际对象。表名和约束名均为示例,需替换为日志中的名称:
SELECT conname, contype, convalidated,
pg_get_constraintdef(oid) AS definition
FROM pg_constraint
WHERE conrelid = to_regclass('public.measurements')
AND conname = 'measurements_value_check';
确认 contype = 'c',并对照完整表达式及 convalidated。无返回行时检查数据库、schema 和名称,别在另一个同名表上处理。已经标为有效的约束再次执行验证不会重新扫描所有数据,不能把它用作每次巡检的通用工具。
按条件为假查旧行,保留 NULL 语义
假设真实约束就是 CHECK (value < 50),以下查询只读取不满足它的示例记录:
SELECT id, value
FROM public.measurements
WHERE (value < 50) IS FALSE
ORDER BY id
LIMIT 100;
CHECK 在表达式为假时拒绝行;为真或 SQL 空值时通过。这里用 IS FALSE 对齐这个规则。value 为 NULL 时不会被这一查询选中;若业务还要求非空,应另查非空约束,不能擅自把 IS FALSE 改成 IS NOT TRUE,把原本允许的缺失值也列为违规。
实际表达式有多列、类型转换或函数时,要按完整表达式检查,不能只挑其中一个比较。表达式本身若求值报错,应先处理该错误。CHECK 也不适合依赖其他行或其他表中会变化的状态;这样的数据规则需要重新选择约束模型。
修数据之前确认新规则是否合理
旧值可能来自早期业务允许的范围,也可能是导入错误。先限定受影响记录、保留原始值和备份,再决定修正数据还是调整新规则。不要为让验证通过一律删掉记录、填零或截断到最大值;这些操作会改变业务信息和关联结果。
保留已添加的合理 CHECK,可以继续挡住新的违规插入或更新。一次只读查询没有结果,不保证之后仍没有冲突;还要核对实际写入入口及约束是否一直生效。数据修正也需要自己的事务和回退记录。
准备好后,再针对准确对象安排 ALTER TABLE ... VALIDATE CONSTRAINT ...。它仍需要扫描数据并取得 SHARE UPDATE EXCLUSIVE 锁,大表可能持续较久,也可能等待其他维护操作。不要把 NOT VALID 理解为整项迁移没有锁或扫描成本。
验证失败时,读取完整错误并确认事务状态,不能手改系统目录中的 convalidated。验证成功后重新查询标志,核对允许值能正常写入、违规值被拒绝,以及 NULL 是否符合原定模型。SQL 工具显示“约束存在”,不足以替代这些检查。
撤销或放宽约束会重新允许旧规则之外的数据,应作为规则变更处理。删除约束也不会恢复此前修正或删除的记录;原值备份需要单独保留。


