Вимкнення всіх обмежень та перевірок таблиці під час відновлення дампа


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створює дамп, який можна відновити в порожній БД.
dezso

1
@dezso правильно, це були помилки, я оновив питання. Дамп, на жаль, не може бути відновлений у порожній БД через причини, які я цитую.
Маркус Юній Брут

Питання дуже потребує вашої версії Postgres. Це повинно бути очевидним без того, щоб я це вказував.
Ервін Брандштеттер

@ErwinBrandstetter Я можу відтворити ту саму проблему 9.6, див. Bugs.debian.org/cgi-bin/bugreport.cgi?bug=859033 для іншого прикладу (більш реальний, трохи більший,
MWE

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

Простим рішенням буде скинути db без даних для таблиці, що порушує:

--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, також не можна відтворювати за допомогою тригерів замість перевірок.
Marcus Junius Brutus

1
@MarcusJuniusBrutus: Оскільки обмеження перевірки оцінюються для всіх рядків, які вже є у таблиці про створення, тоді як тригери виконуються лише у визначених подіях.
Erwin Brandstetter

Я відтворив точну логіку схеми за допомогою тригерів. Використовуючи тригери, відновлення дійсно успішно, але виявляється, що це лише завдяки тому, що pg_dumpдодає тригери в кінці файлу дампа, тоді як він створює CHECKs як частину CREATE TABLEкоманди. Тож відновлення могло б досягти успіху і у випадку перевірки, якби pg_dumpінструмент використовував інший підхід. Я не бачу, чому мій DDL в порядку, якщо я використовую тригери, але це не так, якщо я використовую перевірки, оскільки в обох випадках реалізована однакова логіка (ви можете бачити версію сценарію, використовуючи тригери в моїй власній відповіді).
Маркус Юній Брут

1
@MarcusJuniusBrutus: якщо ви вважаєте, що вам pg_dumpслід створити різні DDL для обмежень перевірки (наприклад, додати їх у кінці), слід опублікувати його в списку розсилки Postgres як запит на покращення. Але я погоджуюся з Ервіном, що ви неправильно використовуєте контрольні обмеження для того, для чого вони не були розроблені. Тому я не очікував, що запит на зміну буде реалізований найближчим часом. Btw: ваш 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 виправлена.
Маркус Юній Брут
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.