Je pense que je voulais probablement ajouter ce commentaire sur la réponse précédente, à propos de deux déclarations distinctes. C'était il y a plus d'un an, donc je ne suis plus totalement sûr.
La requête basée sur wCTE ne résout pas vraiment le problème qu'il est censé résoudre, mais après l'avoir réexaminée plus d'un an plus tard, je ne vois pas la possibilité de mises à jour perdues dans la version wCTE.
(Notez que toutes ces solutions ne fonctionneront bien que si vous essayez de changer exactement une ligne avec chaque transaction. Dès que vous essayez de faire plusieurs changements dans une transaction, les choses deviennent compliquées en raison de la nécessité de réessayer les boucles lors des annulations. Au minimum vous devez utiliser un point de sauvegarde entre chaque modification.)
Version à deux états sous réserve de mises à jour perdues
La version qui utilise deux instructions distinctes est sujette à des mises à jour perdues à moins que l'application vérifie le nombre de lignes affectées de l' UPDATE
instruction et de l' INSERT
instruction et réessaye si les deux sont nuls.
Imaginez ce qui se passe si vous avez deux transactions READ COMMITTED
isolément.
- TX1 exécute le
UPDATE
(aucun effet)
- TX1 exécute le
INSERT
(insère une ligne)
- TX2 exécute le
UPDATE
(aucun effet, la ligne insérée par TX1 n'est pas encore visible)
- TX1
COMMIT
s.
- TX2 exécute le
INSERT
, * qui obtient un nouvel instantané qui peut voir la ligne validée par TX1. La EXISTS
clause renvoie true, car TX2 peut maintenant voir la ligne insérée par TX1.
TX2 n'a donc aucun effet. À moins que l'application vérifie le nombre de lignes à partir de la mise à jour et de l'insertion et réessaye si les deux signalent zéro ligne, elle ne saura pas que la transaction n'a eu aucun effet et se poursuivra joyeusement.
La seule façon dont il peut vérifier les nombres de lignes affectés est de l'exécuter en tant que deux instructions distinctes plutôt qu'en plusieurs instructions, ou d'utiliser une procédure.
Vous pouvez utiliser l' SERIALIZABLE
isolement, mais vous aurez toujours besoin d'une boucle de relance pour faire face aux échecs de sérialisation.
La version wCTE protège contre le problème des mises à jour perdues car le INSERT
est conditionnel à l' UPDATE
affectation de lignes, plutôt qu'à une requête distincte.
Le wCTE n'élimine pas les violations uniques
La version CTE inscriptible n'est toujours pas un upsert fiable.
Considérez deux transactions qui s'exécutent simultanément.
Les deux exécutent la clause VALUES.
Maintenant, les deux exécutent la UPDATE
partie. Puisqu'il n'y a pas de lignes correspondant à la UPDATE
clause s where, les deux renvoient un ensemble de résultats vide de la mise à jour et n'apportent aucune modification.
Maintenant, les deux exécutent la INSERT
portion. Étant donné que les UPDATE
lignes retournées zéro pour les deux requêtes, les deux tentent de INSERT
la ligne.
On réussit. On jette une violation unique et abandonne.
Ce n'est pas une source de préoccupation pour la perte de données tant que l'application vérifie les résultats d'erreur de ses requêtes (c'est-à-dire toute application décemment écrite) et réessaie, mais cela ne rend pas la solution meilleure que les versions existantes à deux instructions. Il n'élimine pas la nécessité d'une boucle de nouvelle tentative.
L'avantage du wCTE par rapport à la version existante à deux instructions est qu'il utilise la sortie de la UPDATE
pour décider si INSERT
, au lieu d'utiliser une requête distincte sur la table. C'est en partie une optimisation, mais cela protège en partie contre un problème avec la version à deux instructions qui provoque des mises à jour perdues; voir ci-dessous.
Vous pouvez exécuter le wCTE de manière SERIALIZABLE
isolée, mais vous obtiendrez alors simplement des échecs de sérialisation au lieu de violations uniques. Cela ne changera pas la nécessité d'une boucle de nouvelle tentative.
Le WCTE ne pas semble être vulnérable aux mises à jour perdues
Mon commentaire a suggéré que cette solution pourrait entraîner la perte de mises à jour, mais après examen, je pense que je me suis peut-être trompé.
Il y a plus d'un an, et je ne me souviens pas des circonstances exactes, mais je pense que j'ai probablement manqué le fait que les index uniques ont une exception partielle des règles de visibilité des transactions afin de permettre à une transaction d'insertion d'attendre qu'une autre s'insère ou roule avant de continuer.
Ou peut-être ai-je manqué le fait que INSERT
dans le wCTE est conditionnel à la présence UPDATE
de lignes affectées, et non pas à la présence ou non de la ligne candidate dans le tableau.
INSERT
S en conflit sur un index unique attend la validation / restauration
Supposons qu'une copie de la requête s'exécute, en insérant une ligne. Le changement n'est pas encore engagé. Le nouveau tuple existe dans le tas et l'index unique, mais il n'est pas encore visible pour les autres transactions, quels que soient les niveaux d'isolement.
Maintenant, une autre copie de la requête s'exécute. La ligne insérée n'est pas encore visible car la première copie n'est pas validée, donc la mise à jour ne correspond à rien. La requête continuera pour tenter une insertion, qui verra qu'une autre transaction en cours insère cette même clé et bloquera l'attente de validation ou de restauration de cette transaction .
Si la première transaction est validée, la seconde échouera avec une violation unique, comme indiqué ci-dessus. Si la première transaction est annulée, la seconde procédera à la place à son insertion.
Le fait d' INSERT
être dépendant du nombre de UPDATE
lignes protège contre les mises à jour perdues
Contrairement au cas à deux déclarations, je ne pense pas que le wCTE soit vulnérable aux mises à jour perdues.
Si le UPDATE
n'a aucun effet, le INSERT
sera toujours exécuté, car il est strictement conditionnel à ce qu'il ait UPDATE
fait quelque chose, pas à l'état de la table externe. Il peut donc toujours échouer avec une violation unique, mais il ne peut pas manquer silencieusement d'avoir aucun effet et perdre complètement la mise à jour.