Vos options sont :
-
Exécuter en
SERIALIZABLEisolation. Les transactions interdépendantes seront abandonnées lors de la validation en raison d'un échec de sérialisation. Vous recevrez beaucoup de spam dans le journal des erreurs et vous ferez de nombreuses tentatives, mais cela fonctionnera de manière fiable. -
Définir un
UNIQUEcontrainte et réessayer en cas d'échec, comme vous l'avez noté. Mêmes problèmes que ci-dessus. -
S'il existe un objet parent, vous pouvez
SELECT ... FOR UPDATEl'objet parent avant de faire votremaxrequête. Dans ce cas, vous devezSELECT 1 FROM bar WHERE bar_id = $1 FOR UPDATE. Vous utilisezbarcomme un verrou pour tous lesfoos avec cebar_id. Vous pouvez alors savoir que vous pouvez continuer en toute sécurité, tant que chaque requête qui effectue l'incrémentation de votre compteur le fait de manière fiable. Cela peut très bien fonctionner.Cela fait toujours une requête agrégée pour chaque appel, ce qui (selon l'option suivante) n'est pas nécessaire, mais au moins cela ne spamme pas le journal des erreurs comme les options ci-dessus.
-
Utilisez une table de comptoir. C'est ce que je ferais. Soit en
bar, ou dans une table d'appoint commebar_foo_counter, obtenez un ID de ligne à l'aide deUPDATE bar_foo_counter SET counter = counter + 1 WHERE bar_id = $1 RETURNING counterou l'option la moins efficace si votre framework ne peut pas gérer
RETURNING:SELECT counter FROM bar_foo_counter WHERE bar_id = $1 FOR UPDATE; UPDATE bar_foo_counter SET counter = $1;Ensuite, dans la même transaction , utilisez la ligne de compteur générée pour le
number. Lorsque vous validez, la ligne de la table de compteur pour cebar_idest déverrouillé pour la prochaine requête à utiliser. Si vous annulez, la modification est annulée.
Je recommande l'approche du compteur, en utilisant une table latérale dédiée pour le compteur au lieu d'ajouter une colonne à bar . C'est plus propre à modéliser, et cela signifie que vous créez moins de ballonnement de mise à jour dans bar , ce qui peut ralentir les requêtes à bar .