Vô hiệu hóa tất cả các ràng buộc và kiểm tra bảng trong khi khôi phục kết xuất


19

Tôi đã nhận được một bãi chứa cơ sở dữ liệu PostgreQuery của mình với:

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

Sau đó tôi tiến hành khôi phục trong cơ sở dữ liệu khác với:

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

Vấn đề của tôi là cơ sở dữ liệu chứa các ràng buộc tham chiếu, kiểm tra và kích hoạt và một số trong số đó (kiểm tra có vẻ như cụ thể) không thành công trong quá trình khôi phục do thông tin không được tải theo thứ tự sẽ khiến các kiểm tra đó được tôn vinh. Chẳng hạn, việc chèn một hàng trong một bảng có thể được liên kết với CHECKmột plpgsqlhàm gọi hàm kiểm tra xem một điều kiện có giữ trong một số bảng không liên quan khác hay không. Nếu bảng sau không được tải psqltrước bảng trước, sẽ xảy ra lỗi.

Sau đây là một SSCCE tạo ra một cơ sở dữ liệu như vậy một khi đã bị hủy với pg_dumpkhông thể được khôi phục:

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

Có cách nào để vô hiệu hóa (từ dòng lệnh) tất cả các ràng buộc như vậy trong quá trình khôi phục kết xuất và cho phép chúng trở lại sau đó không? Tôi đang chạy PostgreSQL 9.1.


Tôi tự hỏi, AFAIK không có -X-dlựa chọn cho pg_dump. pg_dumptạo ra một dump đó restorable trong một DB trống.
dezso

1
@dezso đúng, đây là những lỗi chính tả, tôi đã cập nhật câu hỏi. Đáng buồn thay, không đáng tin cậy trong một DB trống rỗng vì những lý do tôi đang trích dẫn.
Marcus Junius Brutus

Câu hỏi rất cần phiên bản Postgres của bạn. Điều này nên rõ ràng mà không cần tôi chỉ ra.
Erwin Brandstetter

@ErwinBrandstetter Tôi có thể tái tạo vấn đề tương tự vào ngày 9.6, xem bug.debian.org/cgi-bin/ormsreport.cgi?orms=859033 để biết ví dụ khác (thế giới thực hơn, lớn hơn một chút, MWE)
mirabilos

1
@mirabilos: Tôi muốn nói: nếu bạn sử dụng một hàm tham chiếu các bảng khác trong một CHECKràng buộc, thì tất cả các bảo đảm đều bị hủy, vì điều đó không được hỗ trợ chính thức, chỉ cần dung thứ. Nhưng tuyên bố các CHECKràng buộc NOT VALIDlàm cho nó hoạt động đối với tôi về mọi mặt. Có thể có những trường hợp góc mà tôi chưa bao giờ chạm vào ...
Erwin Brandstetter

Câu trả lời:


17

Vì vậy, bạn tìm kiếm các bảng khác trong một CHECKràng buộc .

CHECKcác ràng buộc được cho là để chạy IMMUTABLEkiểm tra. Những gì vượt qua OK cho một hàng tại một thời điểm sẽ vượt qua OK bất cứ lúc nào . Đó là cách các CHECKràng buộc được định nghĩa trong tiêu chuẩn SQL. Đó cũng là lý do cho sự hạn chế này ( theo tài liệu ):

Hiện tại, các CHECKbiểu thức không thể chứa các truy vấn con cũng như tham chiếu đến các biến khác ngoài các cột của hàng hiện tại.

Bây giờ, các biểu thức trong các CHECKràng buộc được phép sử dụng các hàm, thậm chí các hàm do người dùng định nghĩa. Những người nên được giới hạn IMMUTABLEchức năng, nhưng Postgres hiện không thực thi điều này. Theo cuộc thảo luận liên quan này về tin tặc pssql , một lý do là cho phép các tài liệu tham khảo đến thời điểm hiện tại, không phải IMMUTABLEbản chất.

Nhưng bạn đang tìm kiếm các hàng của một bảng khác, hoàn toàn vi phạm cách các CHECKràng buộc được cho là hoạt động. Tôi không ngạc nhiên khi pg_dumpkhông cung cấp cho điều này.

Di chuyển séc của bạn trong một bảng khác sang một trình kích hoạt (đây là công cụ phù hợp) và nó sẽ hoạt động với các phiên bản hiện đại của Postgres.

PostgreSQL 9.2 trở lên

Mặc dù điều trên đúng với mọi phiên bản của Postgres, một số công cụ đã được giới thiệu với Postgres 9.2 để giúp giải quyết tình huống của bạn:

tùy chọn pg_dump --exclude-table-data

Một giải pháp đơn giản là kết xuất db mà không có dữ liệu cho bảng vi phạm với:

--exclude-table-data=my_schema.my_tbl

Sau đó, chỉ thêm dữ liệu cho bảng này vào cuối bãi chứa với:

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

Nhưng các biến chứng với các ràng buộc khác trên cùng một bảng có thể xảy ra. Có một giải pháp thậm chí tốt hơn :

NOT VALID

NOT VALIDsửa đổi cho các ràng buộc. Chỉ có sẵn cho các ràng buộc FK trong v9.1, nhưng điều này đã được mở rộng thành các CHECKràng buộc trong 9.2. Mỗi tài liệu:

Nếu ràng buộc được đánh dấu NOT VALID, kiểm tra ban đầu có khả năng dài để xác minh rằng tất cả các hàng trong bảng thỏa mãn ràng buộc được bỏ qua. Ràng buộc vẫn sẽ được thi hành đối với các lần chèn hoặc cập nhật tiếp theo [...]

Một tệp kết xuất postgres đơn giản bao gồm ba "phần":

  • pre_data
  • data
  • post-data

Postgres 9.2 cũng giới thiệu một tùy chọn để kết xuất các phần riêng biệt với -- section=sectionname, nhưng điều đó không giúp ích gì cho vấn đề trong tay.

Đây là nơi ta cảm thấy thú vị. Mỗi tài liệu:

Các mục sau dữ liệu bao gồm các định nghĩa về chỉ mục, trình kích hoạt, quy tắc và các ràng buộc khác với các ràng buộc kiểm tra được xác thực . Các mục trước dữ liệu bao gồm tất cả các mục định nghĩa dữ liệu khác.

Nhấn mạnh đậm của tôi.
Bạn có thể thay đổi CHECKràng buộc vi phạm thành NOT VALID, điều này sẽ chuyển ràng buộc sang post-dataphần. Thả và tạo lại:

ALTER TABLE a DROP CONSTRAINT a_constr_1;
ALTER TABLE a ADD  CONSTRAINT a_constr_1 CHECK (fail_if_b_empty()) NOT VALID;

Điều này sẽ giải quyết vấn đề của bạn. Bạn thậm chí có thể để lại các ràng buộc trong trạng thái đó , vì điều đó phản ánh tốt hơn những gì nó thực sự làm: kiểm tra các hàng mới, nhưng không đảm bảo cho dữ liệu hiện có. Không có gì sai với một NOT VALIDràng buộc kiểm tra. Nếu bạn thích, bạn có thể xác nhận nó sau:

ALTER TABLE a VALIDATE CONSTRAINT a_constr_1;

Nhưng sau đó bạn trở lại trạng thái ante.


Tôi đã làm phong phú câu hỏi với SSCCE cho thấy cơ sở dữ liệu không thể khôi phục. Tôi hiểu những gì bạn đang nói, tuy nhiên tôi không hiểu tại sao tình huống có vấn đề mà tôi đưa ra trong SSCCE của mình cũng có thể được sao chép bằng các kích hoạt thay vì kiểm tra.
Marcus Junius Brutus

1
@MarcusJuniusBrutus: Bởi vì các ràng buộc kiểm tra được ước tính cho tất cả các hàng đã có trong bảng khi tạo, trong khi các trình kích hoạt chỉ chạy trên các sự kiện đã xác định.
Erwin Brandstetter

Tôi đã tái tạo logic lược đồ chính xác bằng cách sử dụng các kích hoạt. Sử dụng các kích hoạt, việc khôi phục thực sự thành công nhưng có vẻ như điều này chỉ là do thực tế là pg_dumpthêm các kích hoạt ở cuối tệp kết xuất, trong khi nó tạo ra CHECKs như một phần của CREATE TABLElệnh. Vì vậy, việc khôi phục cũng có thể đã thành công cho trường hợp kiểm tra nếu pg_dumpcông cụ sử dụng một cách tiếp cận khác. Tôi không biết tại sao DDL của tôi vẫn ổn nếu tôi sử dụng trình kích hoạt nhưng không ổn nếu tôi sử dụng kiểm tra vì logic chính xác được thực hiện trong cả hai trường hợp (bạn có thể xem phiên bản của tập lệnh bằng cách sử dụng trình kích hoạt trong câu trả lời của riêng tôi).
Marcus Junius Brutus

1
@MarcusJuniusBrutus: nếu bạn cảm thấy pg_dumpnên tạo DDL khác nhau cho các ràng buộc kiểm tra (ví dụ: thêm tất cả chúng vào cuối), bạn nên đăng nó vào danh sách gửi thư của Postgres dưới dạng yêu cầu nâng cao. Nhưng tôi đồng ý với Erwin rằng bạn đang sử dụng sai các ràng buộc kiểm tra cho một cái gì đó mà chúng không được thiết kế cho. Vì vậy, tôi không mong đợi rằng yêu cầu thay đổi sẽ được thực hiện trong tương lai gần. Btw: SSCCE của bạn sẽ được mô hình hóa tốt hơn bằng cách sử dụng khóa ngoại giữa hai bảng.
a_horse_with_no_name

@MarcusJuniusBrutus: Có giải pháp cho Postgres 9.2 trở lên. Đó là lý do tại sao phiên bản Postgres của bạn rất quan trọng. Có lẽ nâng cấp là một lựa chọn cho bạn? Postgres 9.1 dù sao cũng già đi ...
Erwin Brandstetter

2

Có vẻ như điều này là do cách pg_dumptạo ra bãi rác. Nhìn vào kết xuất thực tế tôi thấy rằng CHECKràng buộc đã có trong tệp kết xuất bằng cú pháp là một phần của CREATE TABLElệnh:

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

Điều này tạo ra sự thất bại khi khôi phục cơ sở dữ liệu khi kiểm tra được đặt đúng chỗ trước khi bảng ahoặc bảng bcó bất kỳ dữ liệu nào trong đó. Tuy nhiên, nếu tệp kết xuất được chỉnh sửa và thay vào đó CHECKđược thêm bằng cú pháp sau, ở cuối tệp kết xuất:

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

... Sau đó không có vấn đề gì trong việc phục hồi.

Logic chính xác tương tự có thể được thực hiện bằng cách sử dụng TRIGGERnhư trong đoạn mã sau:

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

Tuy nhiên, trong trường hợp này, pg_dumptạo (theo mặc định) trình kích hoạt ở cuối tệp kết xuất (chứ không phải trong CREATE TABLEcâu lệnh như trong trường hợp kiểm tra) và do đó quá trình khôi phục thành công.


không thấy bất kỳ kích hoạt nào trong ví dụ của bạn
Sam Watkins

1
@SamWatkins lỗi sao chép-dán, đã sửa nó.
Marcus Junius Brutus
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.