Отключите все ограничения и проверки таблиц при восстановлении дампа

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_dump. pg_dumpсоздает дамп, который можно восстановить в пустой БД.
Дезсо
1
@ dezso правильно, это были опечатки, я обновил вопрос. К сожалению, дамп не может быть восстановлен в пустой БД по причинам, которые я цитирую.
Маркус Юний Брут
Вопрос остро нуждается в вашей версии Postgres. Это должно быть очевидно без моего указания.
Эрвин Брандштеттер,
@ErwinBrandstetter Я могу воспроизвести ту же проблему на 9.6, см. Bugs.debian.org/cgi-bin/bugreport.cgi?bug=859033 для другого примера (более реальный, немного больше, MWE)
mirabilos
1
@mirabilos: Я бы сказал: если вы используете функцию, которая ссылается на другие таблицы в CHECKограничении, то все гарантии аннулируются, потому что это официально не поддерживается, просто допускается. Но объявление CHECKограничения NOT VALIDзаставило меня работать во всех отношениях. Могут быть угловые случаи, которых я никогда не трогал ...
Эрвин Брандштеттер,

Ответы:

17

Итак, вы ищите другие таблицы в CHECKограничении .

CHECKограничения должны запускать IMMUTABLEпроверки. То, что проходит ОК для строки за один раз, должно пройти ОК в любое время. Вот как CHECKограничения определены в стандарте SQL. Это также причина этого ограничения ( согласно документации ):

В настоящее время CHECKвыражения не могут содержать подзапросы или ссылаться на переменные, кроме столбцов текущей строки.

Теперь выражениям в CHECKограничениях разрешено использовать функции, даже пользовательские функции. Они должны быть ограничены IMMUTABLEфункциями, но Postgres в настоящее время не применяет это. Согласно этому связанному обсуждению pgsql-хакеров , одной из причин является разрешение ссылок на текущее время, что не 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модификатор для ограничений. Доступно только для ограничения FK в v9.1, но это было расширено до CHECKограничений в 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;
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, также не может быть воспроизведена с помощью триггеров вместо проверок.
Маркус Юний Брутус
1
@MarcusJuniusBrutus: потому что проверочные ограничения оцениваются для всех строк, которые уже находятся в таблице на момент создания, а триггеры запускаются только для определенных событий.
Эрвин Брандштеттер
Я воспроизвел точную логику схемы, используя триггеры. Используя триггеры, восстановление действительно успешно, но кажется, что это только из-за того, что pg_dumpдобавляет триггеры в конец файла дампа, в то время как он создает CHECKs как часть CREATE TABLEкоманды. Таким образом, восстановление могло бы также быть успешным для контрольного случая, если pg_dumpинструмент использовал другой подход. Я не понимаю, почему мой DDL в порядке, если я использую триггеры, но не в порядке, если я использую проверки, поскольку в обоих случаях реализована точно такая же логика (вы можете увидеть версию сценария с использованием триггеров в моем собственном ответе).
Маркус Юний Брутус
1
@MarcusJuniusBrutus: если вы чувствуете, что pg_dumpдолжны генерировать разные DDL для проверочных ограничений (например, добавляя их все в конце), вы должны опубликовать это в списке рассылки Postgres как запрос на расширение. Но я согласен с Эрвином в том, что вы неправильно используете проверочные ограничения для того, для чего они не были предназначены. Поэтому я не ожидаю, что этот запрос на изменение будет реализован в ближайшем будущем. Кстати, ваш SSCCE будет лучше смоделирован с использованием внешнего ключа между двумя таблицами.
a_horse_with_no_name
@MarcusJuniusBrutus: есть решения для Postgres 9.2 или новее. Вот почему ваша версия Postgres имеет решающее значение. Может быть, обновление для вас? Postgres 9.1 все равно стареет ...
Эрвин Брандштеттер
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операторе, как в случае проверки), и поэтому восстановление завершается успешно.

Маркус Юний Брут
источник
не вижу никакого триггера в вашем примере
Сэм Уоткинс,
1
@SamWatkins ошибка копирования-вставки, исправлена.
Маркус Юний Брут