恢复转储时禁用所有约束和表检查

Mar*_*tus 22 postgresql database-design postgresql-9.1 pg-dump check-constraints

我已经获得了我的 PostgreSQL 数据库的转储:

pg_dump -U user-name -d db-name -f dumpfile
Run Code Online (Sandbox Code Playgroud)

然后我继续在另一个数据库中恢复:

psql X -U postgres  -d db-name-b -f dumpfile
Run Code Online (Sandbox Code Playgroud)

我的问题是数据库包含引用约束、检查和触发器,其中一些(特别是检查)在恢复过程中失败,因为信息没有按照会导致这些检查得到遵守的顺序加载。例如,在表中插入一行可能与CHECK调用一个plpgsql函数相关联,该函数检查某个条件是否在某个其他不相关的表中成立。如果后者没有psql在前者之前加载,则会发生错误。

下面是一个 SSCCE,它产生了这样一个一旦转储pg_dump就无法恢复的数据库:

CREATE OR REPLACE FUNCTION fail_if_b_empty () RETURNS BOOLEAN AS $$
    SELECT EXISTS (SELECT 1 FROM b)
$$ LANGUAGE SQL;

CREATE TABLE IF NOT EXISTS a (
     i              INTEGER                    NOT NULL
);

INSERT INTO a(i) VALUES (0),(1);
CREATE TABLE IF NOT EXISTS b (
    i  INTEGER NOT NULL
);
INSERT INTO b(i) VALUES (0);

ALTER TABLE a ADD CONSTRAINT a_constr_1 CHECK (fail_if_b_empty());
Run Code Online (Sandbox Code Playgroud)

有没有办法在转储恢复期间禁用(从命令行)所有此类约束并在之后再次启用它们?我正在运行 PostgreSQL 9.1。

Erw*_*ter 22

因此,您在CHECK约束中查找其他表。

CHECK约束应该运行IMMUTABLE检查。一次通过OK的应该随时通过。这就是CHECKSQL 标准中定义约束的方式。这也是手册中进行此限制的原因:

目前,CHECK表达式不能包含子查询,也不能引用当前行列以外的变量。

尽管如此,CHECK约束中的表达式仍然可以使用函数,甚至是用户定义的函数。那些应该IMMUTABLE,但是 Postgres 目前没有强制执行这个。根据pgsql-hackers 上的相关讨论,一个原因是允许引用当前时间,这不是IMMUTABLE本质上的。

但是您正在查找另一个表的行,这完全违反了CHECK约束的工作方式。我对pg_dump未能提供这一点并不感到惊讶。

将您在另一个表中的检查移动到触发器(这是正确的工具),它应该可以与现代版本的 Postgres 一起使用。

PostgreSQL 9.2 或更高版本

虽然上述内容适用于任何版本的 Postgres,但 Postgres 9.2 引入了一些工具来帮助您解决问题:

pg_dump 选项 --exclude-table-data

一个简单的解决方案是为违规表转储没有数据的数据库:

--exclude-table-data=my_schema.my_tbl
Run Code Online (Sandbox Code Playgroud)

然后在转储结束时仅附加此表的数据:

--data-only --table=my_schema.my_tbl
Run Code Online (Sandbox Code Playgroud)

但是,同一张表上的其他约束可能会出现复杂情况。有一个更好的解决方案

解决方案: NOT VALID

在 Postgres 9.1 之前,NOT VALID修改器仅可用于 FK 约束。这被扩展到CHECKPostgres 9.2 中的约束。手册:

如果约束被标记为NOT VALID,则跳过可能冗长的初始检查以验证表中的所有行是否满足约束。该约束仍将针对后续插入或更新强制执行 [...]

一个普通的 Postgres 转储文件由三个“部分”组成:

  • pre_data
  • data
  • post-data

Postgres 9.2 还引入了一个选项,可以使用 单独转储部分-- section=sectionname,但这对解决手头的问题没有帮助。

这就是有趣的地方。手册:

后数据项包括索引、触发器、规则和约束的定义,而 不是经过验证的检查约束。预数据项包括所有其他数据定义项。

大胆强调我的。
您可以将违规CHECK约束更改为NOT VALID,从而将约束移动到该post-data部分。删除并重新创建:

ALTER TABLE a
  DROP CONSTRAINT a_constr_1
, ADD  CONSTRAINT a_constr_1 CHECK (fail_if_b_empty()) NOT VALID;
Run Code Online (Sandbox Code Playgroud)

单个语句最快,并排除并发事务的竞争条件。(单个事务中的两个命令也可以工作。)

这应该可以解决您的问题。您甚至可以将约束保留在该状态,因为这更好地反映了它的实际作用:检查新行,但不对现有数据提供任何保证。NOT VALID检查约束没有任何问题。如果您愿意,可以稍后对其进行验证:

ALTER TABLE a VALIDATE CONSTRAINT a_constr_1;
Run Code Online (Sandbox Code Playgroud)

但随后你又回到了原状。