3

Je suis donc confus au sujet de la gestion des contraintes de clé étrangère dans Postgresql. (version 8.4.4, pour ce que ça vaut).Postgresql: Acquisition de verrou implicite à partir d'une évaluation de contrainte de clé étrangère

Nous avons deux tables, légèrement anonymisées ci-dessous:

device: 
    (id, blah, blah, blah, blah, blah x 50)… 
    primary key on id 
    whooooole bunch of other junk 

device_foo: 
    (id, device_id, left, right) 
    Foreign key (device_id) references device(id) on delete cascade; 
    primary key on id 
    btree index on 'left' and 'right' 

Je me suis donc avec deux fenêtres de base de données pour exécuter certaines requêtes.

db1> begin; lock table device in exclusive mode; 
db2> begin; update device_foo set left = left + 1; 

Les blocs de connexion db2.

Il me semble étrange qu'une mise à jour de la colonne 'left' sur device_stuff soit affectée par l'activité sur la table de périphériques. Mais il est. En fait, si je reviens à DB1:

db1> select * from device_stuff for update; 
      *** deadlock occurs *** 

Le journal des pgsql a les éléments suivants:

blah blah blah deadlock blah. 
CONTEXT: SQL statement "SELECT 1 FROM ONLY "public"."device" x WHERE "id" OPERATOR(pg_catalog.=) $1 FOR SHARE OF X: update device_foo set left = left + 1; 

Je suppose que j'ai deux questions: la première est que je ne comprends pas la mécanisme précis par lequel ce genre de verrouillage se produit. J'ai quelques requêtes utiles pour interroger pg_locks pour voir quel genre de verrous une instruction invoque, mais je n'ai pas été en mesure d'observer ce genre particulier de verrouillage lorsque j'exécute la commande update device_foo en isolation. (Peut-être que je fais quelque chose de mal, cependant.) Je ne peux pas non plus trouver de documentation sur le comportement d'acquisition de verrous des contrôles de contraintes de clé étrangère. Tout ce que j'ai est un message de journal. Dois-je en déduire que toute modification apportée à une ligne va acquérir un verrou de mise à jour sur toutes les tables contre lesquelles elle est importée?

Le deuxième problème est que je voudrais trouver un moyen de faire en sorte que cela n'arrive pas comme ça. Je me retrouve avec des blocages occasionnels dans l'application actuelle. Je voudrais être en mesure d'exécuter de grandes instructions de mise à jour qui affectent toutes les lignes sur device_foo sans acquérir un grand verrou sur la table des périphériques. (Il y a un beaucoup d'accès en cours dans la table device, et il est en quelque sorte un verrou coûteux à obtenir.)

Répondre

0

table de verrouillage en mode exclusif signifie que le processus de nul ne peut lire ce tableau, et la vérification de clé étrangère besoin de lire le dispositif de table.

+0

Mais pourquoi doit-il vérifier les contraintes de clé étrangère? Je ne change aucune colonne de clé étrangère. – fennec

2

L'instruction lock table device in exclusive mode prend un verrou très restrictif sur la table ("exclusive mode"). La modification d'une table contenant une clé étrangère sur une table parent prend un verrou de partage relativement inoffensif sur la table parente (vous ne pouvez pas tronquer une table alors que les lignes la référencant sont potentiellement mises à jour, par exemple).

En fait, en essayant maintenant, je ne peux pas reproduire votre comportement de verrouillage (sur 8.4.4 comme vous êtes). Je l'ai fait:

create table device(device_id serial primary key, value text not null); 
create table device_foo(device_foo_id serial primary key, device_id int not null references device(device_id) on delete cascade, value text not null); 
insert into device(value) values('FOO'),('BAR'),('QUUX'); 
insert into device_foo(device_id, value) select device_id, v.value from (values('mumble'),('grumble'),('fumble')) v(value), device; 

Et puis dans deux connexions simultanées que j'ai:

<1>=# begin; lock table device in exclusive mode; 
<2>=# begin; update device_foo set value = value || 'x'; 

Cela me semble être équivalent à ce que vous faites, mais je ne reçois pas la deuxième verrouillage de la session - il donne immédiatement "UPDATE 9" comme prévu. Insertion dans device_foo blocs, comme vous vous y attendez, ainsi qu'une instruction de mise à jour définissant la colonne device_id. Je peux voir le ExclusiveLock dans pg_locks de la session db1 dans la session db2.Il bloque également si je fais "select * from device for share", ce qui est la déclaration que vous voyez dans l'erreur de blocage. Je ne reçois pas non plus de blocage si je fais un "select * from device_foo for update" de la connexion db1 alors que db2 est bloqué en essayant de mettre à jour la colonne device_id dans device_foo.

La mise à jour d'une ligne marque la ligne comme verrouillée, mais ce verrou n'est pas visible dans pg_locks. Il prend également un verrou sur la table pour verrouiller quiconque tente d'abandonner/tronquer/réindexer la table pendant la mise à jour de l'une de ses lignes.

Pour verrouiller la table device par rapport aux mises à jour simultanées, vous pouvez souhaiter un mode de verrouillage moins strict. Le manual suggère "partager ligne exclusive" pour ce type d'activité. Bien qu'il ne s'agisse que d'un niveau inférieur à "exclusif", il est compatible avec une instruction "select ... for share". Donc, vraiment, la question ouverte est --- qu'est-ce qui émet cette requête "sélectionnez ... pour partager"? : -S Cela ressemble à une déclaration destinée à affirmer l'intégrité d'une clé étrangère, mais je ne peux pas la reproduire.

+0

Il est très intéressant que ce comportement ne semble pas être la norme pour Postgres. (J'étais sous l'impression que c'était). Donc je vais voir si je peux tripoter les tables et voir ce qui les fait cocher. Et, oui, le «mode exclusif» que j'utilisais était d'isoler le problème, vraiment, pas quelque chose que j'aime faire régulièrement. Les problèmes réels se produisent lorsque les instructions 'update' sont en conflit avec d'autres verrous dispersés autour de' device'. – fennec

Questions connexes