由 John Doe 四月 25, 2025
当我们往数据库导入数据时,经常会出现违反约束报错的情况。现在,PostgreSQL 支持以 “NOT VALID” 方式添加非空约束了。
特性提交日志
支持以 “NOT VALID” 方式添加非空约束。
这使得在添加这些约束时无需扫描表,并且在删除或修正了所有违反约束的行之后,验证约束时也无需对表持有排他访问锁。
对于存在无效非空约束的列,执行ALTER TABLE... SET NOT NULL
操作会验证该约束。同时也支持ALTER TABLE.. VALIDATE CONSTRAINT
操作。当父表存在有效约束时,对于子表中是否允许存在无效约束会有各种检查;这应该与我们对已实施 / 未实施约束的处理方式一致。
现在pg_attribute.attnotnull
仅用于指示某列是否存在非空约束;该约束有效与否必须通过查询pg_constraint
来确定。应用程序可以像以前一样继续查询pg_attribute.attnotnull
,但现在即使该值设置为true
,列中也可能存在NULL
行。
pg_dump
处理这些约束的方式,是将无效非空约束的 OID 存储在一个单独的数组中,并运行一个查询来获取它们的属性。常规的表创建 SQL 会完全忽略它们。然后,以与处理 “单独的” CHECK 约束相同的方式来处理这些约束,并在数据加载完成后转储它们。
讨论:https://postgr.es/m/CAGPqQf0KitkNack4F5CFkFi-9Dqvp29Ro=EpcWt=4_hs-Rt+bQ@mail.gmail.com
示例
在深入了解该特性之前,让我们先看看 PostgreSQL 17 及更早版本在创建 “NOT NULL” 约束时是如何处理的。让我们来创建一个简单的表:
select version();
version
----------------------------------------------------------------------------------------------------------
PostgreSQL 17.2 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 14.2.1 20250110 (Red Hat 14.2.1-7), 64-bit
(1 row)
create table t ( a int not null, b text );
postgres=# \dt
Table "public.t"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
a | integer | | not null |
b | text | | |
尝试向该表插入违反约束的数据,当然会失败:
insert into t select null,1 from generate_series(1,2);
ERROR: null value in column "a" of relation "t" violates not-null constraint
DETAIL: Failing row contains (null, 1).
如果想这么做,唯一的选择是删除约束:
alter table t alter column a drop not null;
insert into t select null, 1 from generate_series(1,2);
postgres=# \dt
Table "public.t"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
a | integer | | |
b | text | | |
这种情况的一个使用场景是数据加载。假设你要加载的数据可能会违反约束,但你打算在加载之后手动修正,然后再重新启用约束,就像这样:
update t set a = 1;
alter table t alter column a set not null;
postgres=# \dt
Table "public.t"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
a | integer | | not null |
b | text | | |
insert into t select null, 1 from generate_series(1,2);
ERROR: null value in column "a" of relation "t" violates not-null constraint
DETAIL: Failing row contains (null, 1).
到了 PostgreSQL 18,情况有所变化。从这个版本开始,你有了更多选择。下面的操作和以前的行为一致:
select version();
version
--------------------------------------------------------------------
PostgreSQL 18devel on x86_64-linux, compiled by gcc-14.2.1, 64-bit
(1 row)
create table t ( a int, b text );
alter table t add constraint c1 not null a;
postgres=# \dt
Table "public.t"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
a | integer | | not null |
b | text | | |
insert into t select null, 1 from generate_series(1,2);
ERROR: null value in column "a" of relation "t" violates not-null constraint
DETAIL: Failing row contains (null, 1).
这和上面 PostgreSQL 17 的行为是一样的。但现在你还可以这样操作:
create table t ( a int, b text );
insert into t select null,1 from generate_series(1,2);
alter table t add constraint c1 not null a not valid;
这样我们就创建了一个 “NOT NULL” 约束,并且在创建时不会强制执行该约束。在 PostgreSQL 17 及更早版本中执行同样的操作,会扫描表并强制执行约束:
select version();
version
----------------------------------------------------------------------------------------------------------
PostgreSQL 17.2 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 14.2.1 20250110 (Red Hat 14.2.1-7), 64-bit
(1 row)
create table t ( a int, b text );
insert into t select null,1 from generate_series(1,2);
alter table t add constraint c1 not null a not valid;
ERROR: syntax error at or near "not"
LINE 1: alter table t add constraint c1 not null a not valid;
^
alter table t alter column a set not null;
ERROR: column "a" of relation "t" contains null values
可以看到,旧版本不支持这种语法,添加 “NOT NULL” 约束会扫描表并强制执行约束。 回到 PostgreSQL 18 的实例。由于我们现在的数据会违反约束:
select * from t;
a | b
---+---
| 1
| 1
(2 rows)
postgres=# \dt
Table "public.t"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
a | integer | | not null |
b | text | | |
我们可以手动修正数据,然后再验证约束:
update t set a = 1;
alter table t validate constraint c1;
insert into t values (null, 'a');
ERROR: null value in column "a" of relation "t" violates not-null constraint
DETAIL: Failing row contains (null, a).
非常不错的体验。感谢社区的所有相关人员。
参考
提交日志:https://git.postgresql.org/pg/commitdiff/a379061a22a8fdf421e1a457cc6af8503def6252