web-dev-qa-db-ja.com

最小限の影響で外部キーをON DELETE CASCADEに変更する

ON DELETE NO ACTIONが定義されている既存の外部キーがあります。この外部キーをON DELETE CASCADEに変更する必要があります。トランザクション内でこれを行うことができます:

begin;
alter table posts drop constraint posts_blog_id_fkey;
alter table posts add constraint posts_blog_id_fkey foreign key (blog_id) references blogs (id) on update no action on delete cascade;
commit;

問題は、postsテーブルが大きい(400万行)ことです。これは、外部キーの検証にかなりの時間がかかることを意味します(データベースのコピーでこれをテストしました)。外部キーの削除/追加 postsACCESS EXCLUSIVEロックを取得 。したがって、制約の検証が行われている間ロックが保持されるため、外部キーブロックallへの適切な時間のpostsテーブルへのアクセスを追加します。オンライン移行を実行する必要があります(専用のダウンタイムウィンドウはありません)。

私は2トランザクションを実行して、チェックに時間がかかることを支援できることを知っています。

begin;
alter table posts drop constraint posts_blog_id_fkey;
alter table posts add constraint posts_blog_id_fkey foreign key (blog_id) references blogs (id) on update no action on delete cascade not valid;
commit;

begin;
alter table posts validate constraint posts;
commit;

このアプローチの利点は、ACCESS EXCLUSIVEロックが非常に短時間保持され、制約を削除/追加してから、制約のみを検証することです a SHARE UPDATE EXCLUSIVE on postsおよびROW SHARE lock on blogs 私はPostgres 9.5を使用しているため。

これには欠点がありますか? NOT VALIDを制約に追加すると、既存データは検証されませんが、VALIDATE CONSTRAINTwillの前に挿入/更新された行はチェックされます。外部キーが同じトランザクションで削除/追加されるため、一貫性のないデータが作成される可能性はありますか?

4
TheCloudlessSky

ドキュメントはこれについて NOT VALID について述べています

ADD table_constraint [ NOT VALID ]

このフォームは、CREATE TABLEと同じ構文を使用して、テーブルに新しい制約を追加します。オプションNOT VALIDは、現在、外部キーとCHECK制約でのみ許可されています。制約にNOT VALIDのマークが付けられている場合、テーブル内のすべての行が制約を満たすことを確認するために時間がかかる可能性のある初期チェックがスキップされます。制約は引き続き後続の挿入または更新に対して適用されます(つまり、外部キーの場合、参照されるテーブルに一致する行がない限り失敗します。新しい行が指定されたチェックと一致しない限り、失敗します。制約)。ただし、データベースは、VALIDATE CONSTRAINTオプションを使用して検証されるまで、制約がテーブル内のすべての行に対して保持されるとは想定しません。

あなたの懸念、

NOT VALIDを制約に追加すると、既存のデータは検証されませんが、VALIDATE CONSTRAINTの前に挿入/更新された行はチェックされます。

それらは検証中にのみチェックされます[〜#〜] [〜#〜]検証後にそれを伝えたので、ダウンタイムをスケジュールするまでそれを遅らせることができます。

外部キーが同じトランザクションで削除/追加されるため、一貫性のないデータが作成される可能性はありますか?

いいえ、2番目にNOT VALIDを追加するので、挿入されたすべての行に適用されます[〜#〜] [〜#〜]ステートメントの後に常にあるかのように。 VALIDATIONは、参照される行が存在しない場合にFOREIGN KEYの作成を拒否するためのものです。カスケードとは何もしない、観察する

CREATE TABLE foo
AS
  SELECT 1 AS a;

CREATE TABLE bar
AS
  SELECT a
  FROM ( VALUES (1),(2) )
    AS t(a);

ALTER TABLE foo
  ADD PRIMARY KEY (a);

ALTER TABLE bar
  ADD FOREIGN KEY (a)
  REFERENCES foo
  ON DELETE CASCADE
  NOT VALID;

DELETE FROM foo;


TABLE foo;
 a 
---
(0 rows)

test=# TABLE bar;
 a 
---
 2
(1 row)

この時点で、あなたは見ることができます

  1. barからbarにカスケードされた削除
  2. バーに検証がないことは、invalidの行がまだあることを意味します

(以下に示すように)制約はまだ検証できませんが、カスケード削除の目的では、すべてが適切です。

ALTER TABLE bar VALIDATE CONSTRAINT bar_a_fkey  ;
ERROR:  insert or update on table "bar" violates foreign key constraint "bar_a_fkey"
DETAIL:  Key (a)=(2) is not present in table "foo".

ところであなたはこれを書くことができます

begin;
alter table posts drop constraint posts_blog_id_fkey;
alter table posts add constraint posts_blog_id_fkey foreign key (blog_id) references blogs (id) on update no action on delete cascade not valid;
commit;

このような

alter table posts
  drop constraint posts_blog_id_fkey,
  add constraint posts_blog_id_fkey
    foreign key (blog_id)
    references blogs (id)
    on update no action
    on delete cascade
    not valid;

Txnでラップする必要はありません。また、txnで単一のステートメントをラップする必要はありません-PostgreSQLはMySQLではありません。すべてがすでにトランザクションに対応しています。

3
Evan Carroll