web-dev-qa-db-fra.com

Changer la clé étrangère en ON DELETE CASCADE avec le moins d'impact

J'ai une clé étrangère existante qui a ON DELETE NO ACTION défini. Je dois changer cette clé étrangère en ON DELETE CASCADE. Je peux le faire dans une transaction:

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;

Le problème est que la table posts est grande (4 millions de lignes), ce qui signifie que la validation de la clé étrangère peut prendre un temps non négligeable (je l'ai testé avec une copie de la base de données). Suppression/ajout de la clé étrangère acquiert un ACCESS EXCLUSIVE lock sur posts. Ainsi, l'ajout des blocs de clés étrangères tous donne accès à la table posts pendant un temps décent car le verrou est maintenu pendant la validation des contraintes. Je dois effectuer une migration en ligne (je n'ai pas de fenêtre d'indisponibilité dédiée).

Je sais que je peux effectuer 2 transactions pour aider le contrôle à prendre longtemps:

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;

L'avantage de cette approche est que le ACCESS EXCLUSIVE le verrou est maintenu pendant un temps très court pour supprimer/ajouter la contrainte, puis pour valider la contrainte uniquement a SHARE UPDATE EXCLUSIVE sur posts et ROW SHARE verrouiller blogs puisque je suis sur Postgres 9.5.

Y a-t-il des inconvénients à cela? Je sais que l'ajout de NOT VALID aux contraintes signifie que existant les données ne sont pas validées, mais toutes les lignes insérées/mises à jour avant le VALIDATE CONSTRAINT sera à vérifier. Parce que la clé étrangère est supprimée/ajoutée dans la même transaction, existe-t-il une possibilité de créer des données incohérentes?

4
TheCloudlessSky

Les docs le disent à propos de NOT VALID

ADD table_constraint [ NOT VALID ]

Ce formulaire ajoute une nouvelle contrainte à une table en utilisant la même syntaxe que CREATE TABLE, plus l'option NOT VALID, qui n'est actuellement autorisé que pour les contraintes de clé étrangère et CHECK. Si la contrainte est marquée NOT VALID, la vérification initiale potentiellement longue pour vérifier que toutes les lignes de la table satisfont à la contrainte est ignorée. La contrainte sera toujours appliquée contre les insertions ou mises à jour ultérieures (c'est-à-dire qu'elles échoueront à moins qu'il n'y ait une ligne correspondante dans la table référencée, dans le cas des clés étrangères; et elles échoueront à moins que la nouvelle ligne corresponde à la vérification spécifiée contraintes). Mais la base de données ne supposera pas que la contrainte est valable pour toutes les lignes de la table, jusqu'à ce qu'elle soit validée à l'aide de VALIDATE CONSTRAINT option.

Votre problème,

Je sais que l'ajout de NOT VALID aux contraintes signifie que les données existantes ne sont pas validées, mais toutes les lignes insérées/mises à jour avant le VALIDATE CONSTRAINT sera vérifié.

Ils ne seront vérifiés que lors de la validation [~ # ~] après [~ # ~] vous lui demandez de valider, vous pouvez donc retarder cela jusqu'à ce que vous ayez programmé un temps d'arrêt.

Parce que la clé étrangère est supprimée/ajoutée dans la même transaction, existe-t-il une possibilité de créer des données incohérentes?

Non, car à la seconde où vous ajoutez le NOT VALID il s'applique à toutes les lignes insérées [~ # ~] après [~ # ~] l'instruction comme si elles étaient toujours là. VALIDATION concerne le refus de créer un FOREIGN KEY lorsque les lignes référencées n'existent pas. Cela a rien à voir avec la cascade, observez

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)

À ce stade, vous pouvez voir

  1. la suppression de bar en cascade vers bar
  2. l'absence de validation sur la barre signifie qu'il a toujours une ligne qui est invalide

La contrainte ne peut toujours pas être validée (comme illustré ci-dessous), mais aux fins de la suppression en cascade, tout est bon.

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".

Btw vous pouvez écrire ceci

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;

Comme ça

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;

Vous n'avez pas besoin de l'envelopper dans un txn. Vous n'avez pas non plus à encapsuler une seule instruction dans un txn - PostgreSQL n'est pas MySQL. Tout est déjà transactionnel.

3
Evan Carroll