web-dev-qa-db-fra.com

Pourquoi dois-je avoir une impasse pour une seule requête de mise à jour?

J'ai deux processus qui exécutent du code comme celui-ci en parallèle:

begin;
update foos set unread=false where owner_id=123 and unread=true;
commit;

Cela se traduit par des blocages.

Ma compréhension de ce qui cause des causes est comme le scénario décrit dans cette question , avec des instructions de mise à jour "Interwoven" mettant à jour deux lignes différentes dans un ordre différent. Je ne comprends pas comment une seule déclaration de mise à jour pourrait entraîner une impasse. Je ne suis pas capable de reproduire le scénario deadlock en utilisant deux sessions PSQL parallèles dans mon environnement de développement. Mes suppositions pour pourquoi je ne peux pas le reproduire:

  1. Je vais mal comprendre mon code qui crée l'erreur d'impasse et il y a en fait plusieurs états de mise à jour dans chaque transaction
  2. L'aspect "interwoven" se produit, mais "dans" la déclaration de mise à jour qui couvre plusieurs rangées, il est donc difficile de se reproduire.

Est-il possible que cette seule mise à jour de créer l'impasse?

1
John Bachir

Votre déclaration modifie plusieurs lignes. Chacune de ces lignes est verrouillée lorsqu'elle est mise à jour.

Il est bien possible qu'une déclaration dans une transaction simultanée a déjà enfermé l'une de ces lignes, bloquant votre UPDATE. Si la transaction simultanée essaie ensuite de verrouiller l'une des lignes que votre UPDATE a déjà verrouillé, vous obtenez une impasse.

3
Laurenz Albe

Laurenz a expliqué le mécanisme qui peut conduire à des blocages, et vous avez déjà inclus un lien avec une explication plus détaillée de Kevin:

Voici quelques instructions étape par étape Comment reproduire une impasse - fonctionne avec un bricolage UPDATE de la même manière qu'elle le fait avec SELECT .. FOR UPDATE:

Maintenant, comment éviter le problème?
[.____] Si vous allez mettre à jour une part substantielle ou tout le tableau - et vous pouvez vous permettre de - juste verrouiller la table . Typiquement, ce n'est pas la voie à suivre. Sinon, trois approches différentes:

1. Ordre constant

le manuel a ceci conseille dans le chapitre sur des blocages:

La meilleure défense contre des blocages est généralement de les éviter en étant donné que toutes les applications utilisant une base de données acquièrent des verrous sur plusieurs objets dans un ordre cohérent.

Je ne sais pas pourquoi il y a toujours no ORDER BY pour UPDATE . Mais c'est ce que nous devons travailler avec. Verrouiller les lignes avec SELECT ... FOR UPDATE Dans la même transaction, comme vous avez déjà essayé, comme votre question précédente indique. Vous venez d'oublier l'essentiel déterministe ORDER BY:

BEGIN;
SELECT FROM foos WHERE owner_id = 123 AND unread
ORDER  BY ??? -- any deterministic order, PK would be an obvious candidate
FOR    UPDATE;

UPDATE foos SET unread = false WHERE owner_id = 123 AND unread;
END;

Évidemment, Tous Les transactions potentiellement concurrentes doivent acquérir des verrous dans le même ordre.

2. Passer des rangées verrouillées

Seulement processus des rangées déverrouillées:

BEGIN;
SELECT FROM foos WHERE owner_id = 123 AND unread
-- ORDER BY ???  -- optional in this case
FOR    UPDATE SKIP LOCKED;

UPDATE foos SET unread = false WHERE owner_id = 123 AND unread;
END;

Si vous êtes certain que des lignes sautées ont été traitées par une transaction concurrente qui fait de même, vous avez terminé ici. (Es-tu sûr?)
D'autre, pour vous assurer, suivez un contrôle:

SELECT EXISTS (SELECT FROM foos WHERE owner_id = 123 AND unread);

Les écrivains ne bloquent pas les lecteurs et les lecteurs ne bloquent pas les écrivains, donc ce retour TRUE jusqu'à ce que chaque dernière ligne ait été mise à jour avec succès. boucle Le bloc ci-dessus UPDATE bloque suivi de ceci (avec délai approprié) jusqu'à ce que vous obteniez FALSE. Alors Vous avez terminé.

Peut être moins cher pour les grands ensembles où ORDER BY Ajouterait des coûts importants. OTOH, il peut toujours avoir du sens à ajouter ORDER BY S'il y a un index correspondant ...

3. un à la fois

Semblable à ce qui précède, sauf que seule une seule ligne est mise à jour à la fois. Généralement plus cher mais tout potentiel d'impasse est éliminé - s'il est fait correctement. Considérez ceci lorsque le traitement d'une seule ligne prend déjà beaucoup de temps.

Explication détaillée (surtout également applicable à ce qui précède) et des instructions:

3
Erwin Brandstetter