PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

UPDATE atomique .. SELECT dans Postgres

Bien que la suggestion d'Erwin soit peut-être la plus simple moyen d'obtenir un comportement correct (tant que vous réessayez votre transaction si vous obtenez une exception avec SQLSTATE de 40001), les applications en file d'attente, de par leur nature, ont tendance à mieux fonctionner avec les requêtes bloquant pour avoir une chance de prendre leur tour dans la file d'attente qu'avec l'implémentation PostgreSQL de SERIALIZABLE transactions, ce qui permet une plus grande simultanéité et est un peu plus "optimiste" quant aux risques de collision.

L'exemple de requête dans la question, tel quel, dans la valeur par défaut READ COMMITTED le niveau d'isolement de la transaction permettrait à deux (ou plus) connexions simultanées de "réclamer" la même ligne de la file d'attente. Voici ce qui va se passer :

  • T1 démarre et va jusqu'à verrouiller la ligne dans le UPDATE étape.
  • T2 chevauche T1 dans le temps d'exécution et tente de mettre à jour cette ligne. Il bloque en attendant le COMMIT ou ROLLBACK de T1.
  • T1 s'engage, après avoir "réclamé" la ligne avec succès.
  • T2 essaie de mettre à jour la ligne, trouve que T1 l'a déjà fait, recherche la nouvelle version de la ligne, trouve qu'elle satisfait toujours aux critères de sélection (qui est juste cet id correspondances), et "réclame" également la ligne.

Il peut être modifié pour fonctionner correctement (si vous utilisez une version de PostgreSQL qui autorise le FOR UPDATE clause dans une sous-requête). Ajoutez simplement FOR UPDATE à la fin de la sous-requête qui sélectionne l'identifiant, et cela se produira :

  • T1 démarre et verrouille maintenant la ligne avant de sélectionner l'identifiant.
  • T2 chevauche T1 dans le temps d'exécution et se bloque lors de la tentative de sélection d'un identifiant, en attendant le COMMIT ou ROLLBACK de T1.
  • T1 s'engage, après avoir "réclamé" la ligne avec succès.
  • Au moment où T2 est capable de lire la ligne pour voir l'identifiant, il voit qu'il a été revendiqué, il trouve donc le prochain identifiant disponible.

A la REPEATABLE READ ou SERIALIZABLE niveau d'isolement de la transaction, le conflit d'écriture générerait une erreur, que vous pourriez intercepter et déterminer comme étant un échec de sérialisation basé sur SQLSTATE, puis réessayer.

Si vous souhaitez généralement des transactions SERIALIZABLE mais que vous souhaitez éviter les nouvelles tentatives dans la zone de file d'attente, vous pourrez peut-être y parvenir en utilisant un verrou consultatif.