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


19

我已经获得了PostgreSQL数据库的转储,其中包括:

pg_dump -U user-name -d db-name -f dumpfile

然后我继续使用以下命令在另一个数据库中还原:

psql X -U postgres  -d db-name-b -f dumpfile

我的问题是数据库包含引用约束,检查和触发器,并且其中某些(特别是看起来是检查的)约束在恢复期间失败,因为未按照导致遵守这些检查的顺序来加载信息。例如,在表中插入行可能与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());

有没有一种方法可以在转储恢复期间禁用(从命令行)所有这些约束,然后再将其重新启用?我正在运行PostgreSQL 9.1。


我想知道,AFAIK没有提供-X和的-d选项pg_dumppg_dump产生转储在一个空数据库恢复。
dezso 2014年

1
@dezso对,这些是错别字,我已经更新了问题。可悲的,由于我所引用的原因,该转储无法在空数据库中恢复。
Marcus Junius Brutus 2014年

这个问题非常需要您的Postgres版本。如果没有我指出,这应该是显而易见的。
Erwin Brandstetter

@ErwinBrandstetter我可以在9.6上重现同样的问题,例如,请参见bugs.debian.org/cgi-bin/bugreport.cgi?bug=859033(更多真实世界,稍大一些,MWE)
mirabilos

1
@mirabilos:我要说的是:如果您使用一个在CHECK约束中引用其他表的函数,那么所有保证都将失效,因为这不是官方支持的,只是可以容忍的。但是,声明CHECK约束NOT VALID使其在各个方面都对我有用。可能有些极端的情况我从未碰过……
欧文·布兰德斯特

Answers:


17

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

CHECK约束应该运行IMMUTABLE检查。一次通过OK的内容应随时通过OK 。这就是CHECK在SQL标准中定义约束的方式。这也是该限制的原因(根据文档):

当前,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

然后在转储末尾仅将表的数据附加到:

--data-only --table=my_schema.my_tbl

但是,可能会在同一张桌子上出现其他约束条件带来的并发症。有一个更好的解决方案

NOT VALID

NOT VALID约束的修饰符。仅适用于v9.1中的FK约束,但已扩展至CHECK9.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;
ALTER TABLE a ADD  CONSTRAINT a_constr_1 CHECK (fail_if_b_empty()) NOT VALID;

这应该可以解决您的问题。您甚至可以将约束保持在该状态,因为这可以更好地反映其实际功能:检查新行,但不保证现有数据。NOT VALID检查约束没有错。如果您愿意,可以稍后进行验证:

ALTER TABLE a VALIDATE CONSTRAINT a_constr_1;

但是,您又回到了现状。


我用SSCCE丰富了这个问题,该SSCCE显示了无法还原的数据库。我明白您的意思,但是我不明白为什么我在SSCCE中展示的问题情况也无法通过触发器而不是检查来重现。
Marcus Junius Brutus 2014年

1
@MarcusJuniusBrutus:因为在创建时会检查表中所有行的检查约束,而触发器仅在定义的事件上运行。
Erwin Brandstetter 2014年

我使用触发器重现了确切的架构逻辑。使用触发器,恢复的确确实成功了,但是这似乎仅是由于以下事实pg_dump:在转储文件的末尾添加了触发器,而将CHECKs作为CREATE TABLE命令的一部分创建了。因此,如果该pg_dump工具使用其他方法,则恢复工作也可以成功完成。我看不到为什么我使用触发器时DDL可以正常运行,但是如果我使用检查则DOK不能正常运行,因为在两种情况下都实现了完全相同的逻辑(您可以在自己的答案中看到使用触发器的脚本版本)。
Marcus Junius Brutus

1
@MarcusJuniusBrutus:如果您认为pg_dump应该为检查约束生成不同的DDL(例如,在末尾添加所有DDL),则应将其作为增强请求发布到Postgres邮件列表中。但是,我确实同意Erwin的观点,即您误用了检查约束,而这些约束并不是为他们设计的。因此,我不希望在不久的将来实现更改请求。顺便说一句:您最好在两个表之间使用外键对SSCCE进行建模。
a_horse_with_no_name 2014年

@MarcusJuniusBrutus:有针对Postgres 9.2或更高版本的解决方案。这就是为什么您的Postgres版本至关重要的原因。也许升级是您的选择?无论如何,Postgres 9.1都在老化……
Erwin Brandstetter 2014年

2

看来这是由于pg_dump创建转储的方式引起的。查看实际的转储,我看到CHECK使用该CREATE TABLE命令一部分的语法在转储文件中存在约束:

CREATE TABLE a (
    i integer NOT NULL,
    CONSTRAINT a_constr_1 CHECK (fail_if_b_empty())
);      

由于在表a或表中b包含任何数据之前进行了检查,因此这会在数据库还原时造成故障。但是,如果编辑了转储文件,CHECK则使用以下语法在转储文件的末尾添加转储文件:

ALTER TABLE a ADD CONSTRAINT a_constr_1 CHECK (fail_if_b_empty()); 

...那么恢复就没有问题了。

可以使用TRIGGER以下脚本中的来实现完全相同的逻辑:

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

DROP TABLE IF EXISTS a;

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);

CREATE TRIGGER tr1 AFTER INSERT OR UPDATE ON a
FOR EACH ROW
EXECUTE PROCEDURE fail_if_b_empty();  

但是,在这种情况下,pg_dump(默认情况下)在转储文件的末尾创建触发器(而不是在检查时在CREATE TABLE语句中创建触发器),因此恢复成功。


在您的示例中没有看到任何触发器
Sam Watkins 2015年

1
@SamWatkins复制粘贴错误,已修复。
Marcus Junius Brutus
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.