web-dev-qa-db-fra.com

Pourquoi CTE est-il ouvert aux mises à jour perdues?

Je ne comprends pas ce que Craig Singer voulait dire quand il a commenté:

Cette solution est soumise à des mises à jour perdues si la transaction d'insertion roule en arrière; Il n'y a pas de vérification pour appliquer que la mise à jour a affecté les lignes.

sur https://stackoverflow.com/a/8702291/14731 . Veuillez fournir une séquence d'événements d'échantillons (E.G. Le thread 1 effectue X, filetage 2) qui démontre la manière dont les mises à jour perdues pourraient se produire.

8
Gili

Je pense que je voulais probablement ajouter ce commentaire sur la réponse antérieure, environ deux déclarations distinctes. C'était il y a plus d'un an, alors je ne suis plus sûr de rien.

La requête basée sur le WCTE ne résout pas vraiment le problème que cela est censé, mais après la revue plus d'un an plus tard, je ne vois pas la possibilité de perdre des mises à jour 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 de multiples changements dans une transaction, les choses sont en désordre en raison de la nécessité de réessayer des boucles de rétrostimation. Au minimum Vous auriez besoin d'utiliser un point de sauvegarde entre chaque changement.)

Version à deux inscriptions soumise à des mises à jour perdues.

la version qui utilise deux instructions distinctes est soumise à des mises à jour perdues à moins que l'application vérifie le nombre de lignes affectées à partir de la déclaration UPDATE et de la déclaration INSERT sont zéro.

Imaginez ce qui se passe si vous avez deux transactions en READ COMMITTED isolation.

  • TX1 exécute le UPDATE (aucun effet)
  • TX1 exécute le INSERT (insère une ligne)
  • TX2 exécute le UPDATE (aucun effet, la rangée insérée par TX1 n'est pas encore visible)
  • TX1 COMMITs.
  • TX2 exécute le INSERT, * qui obtient un nouvel instantané pouvant voir la ligne commise par TX1. La clause EXISTS renvoie true, car TX2 peut maintenant voir la ligne insérée par TX1.

Donc, TX2 n'a aucun effet. Sauf si l'application vérifie la ligne de ligne de la mise à jour et de l'insertion et des tentatives si les deux rapportent zéro rangs, il ne saura pas que la transaction n'avait aucun effet et continuera de manière judiciaire.

Le seul moyen de vérifier que les rangées affectées sont de l'exécuter sous forme de deux états distincts plutôt que de plusieurs états multiples, ou d'utiliser une procédure.

Vous pouvez utiliser SERIALIZABLE isolement, mais vous aurez toujours besoin d'une boucle de nouvelle tentative 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 conditionné à savoir si le UPDATE affecte les lignes plutôt que sur une requête séparée.

Le WCTE n'élimine pas les violations uniques

La version CTE wribleable n'est toujours pas fiable UPSert.

Considérez deux transactions qui l'exécutent simultanément.

  • Tous deux exécutent la clause de valeurs.

  • Maintenant, tous les deux exécutent la partie UPDATE. Comme il n'y a pas de lignes correspondant à la clause UPDATEs où la clause, renvoie à la fois des résultats de la mise à jour et ne modifiait pas.

  • Maintenant, les deux exécutent la partie INSERT. Depuis que le UPDATE a renvoyé zéro rangée pour les deux requêtes, les deux tentent de INSERT la ligne.

On réussit. On lance une violation et une avortes uniques.

Ce n'est pas une source de préoccupation concernant la perte de données tant que l'application vérifie les résultats des erreurs de ses requêtes (c'est-à-dire une application décemment écrite) et réapprovisionnement, mais elle ne vaut mieux que les versions à deux étapes existantes. Cela n'élimine pas la nécessité d'une boucle de nouvelle tentative.

L'avantage que le WCTE offre sur la version à deux inscriptions existante est qu'il utilise la sortie du UPDATE pour décider si INSERT, au lieu d'utiliser une requête distincte contre la table. C'est en partie une optimisation, mais elle protège en partie un problème avec la version à deux inscriptions qui provoque des mises à jour perdues; voir ci-dessous.

Vous pouvez exécuter le WCTE en SERIALIZABLE isolement, mais vous obtiendrez ensuite des échecs de sérialisation au lieu de violations uniques. Cela ne changera pas le besoin d'une boucle de nouvelle tentative.

Le WCTE fait non semble être vulnérable aux mises à jour perdues

Mon commentaire a suggéré que cette solution puisse entraîner des mises à jour perdues, mais après avoir examiné que je pense que j'ai peut-être été erronée.

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 un autre à insertion ou à rouler retour avant de continuer.

Ou peut-être j'ai raté le fait que le INSERT dans le WCTE est conditionné à savoir si le UPDATE a affecté toutes les lignes, non pas si la rangée de candidats existe dans la table.

en conflit INSERTs sur un index unique attendre pour commettre/retourner

Dites qu'une copie de la requête fonctionne, 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 par d'autres transactions, quels que soient les niveaux d'isolement.

Maintenant, une autre copie de la requête fonctionne. La ligne insérée n'est pas encore visible car la première copie n'a pas été commise, la mise à jour ne correspond à rien. La requête continuera à tenter un insert, ce qui verra qu'une autre transaction en cours inserve la même clé et bloquera l'attente de cette transaction pour commettre ou rouler.

Si la première transaction s'engage, le second échouera avec une violation unique, par ce qui précède. Si la première transaction roule à nouveau, la seconde procédera à son insertion.

Le INSERT étant dépendant du UPDATE RowCount protège contre les mises à jour perdues

Contrairement à l'affaire à deux relevé, 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 sur le fait que le UPDATE a fait quelque chose, pas sur l'état de la table externe. Donc, il peut encore échouer avec une violation unique, mais il ne peut pas manquer silencieusement d'avoir un effet et de perdre la mise à jour entièrement.

14
Craig Ringer