web-dev-qa-db-fra.com

Pourquoi est-ce que j'obtiens une "transaction d'isolement d'instantané abandonnée en raison d'un conflit de mise à jour"?

Nous avons deux tables

  1. Parent (Id int identité, Date datetime, Nom nvarchar)
  2. Enfant (Id int identité, ParentId int, Date datetime, Nom nvarchar)

L'enfant ayant une relation de clé étrangère avec le parent.

Nous avons activé l'isolement des snapshots validés au niveau de la base de données.

Nous n'insérons et ne supprimons que des lignes pour le parent et l'enfant (aucune mise à jour)

Nous avons un processus (transaction) qui supprime les anciennes données de l'enfant (puis du parent)

Nous avons plusieurs autres processus (transactions) qui insèrent de nouvelles données dans Parent (puis dans Child)

Le processus de suppression régulièrement (mais pas tout le temps) est annulé, même si le processus d'insertion n'insère pas de nouvelles lignes enfants qui se réfèrent aux lignes parent que la suppression souhaite supprimer - il crée simplement de nouvelles lignes parentales et une ou plusieurs nouvelles lignes enfants qui font référence au nouveau parent

L'erreur lors de la suppression des lignes parentes est:

Transaction d'isolement d'instantané abandonnée en raison d'un conflit de mise à jour. Vous ne pouvez pas utiliser l'isolement d'instantané pour accéder à la table "dbo.Child" directement ou indirectement dans la base de données "Test" pour mettre à jour, supprimer ou insérer la ligne qui a été modifiée ou supprimée par une autre transaction. Relancez la transaction ou modifiez le niveau d'isolement pour l'instruction update/delete.

Je suis conscient que les gens suggèrent d'avoir un index sur la colonne de clé étrangère - nous préférerions ne pas avoir à le faire idéalement (pour des raisons d'espace/de performance) - à moins que ce soit le seul moyen fiable pour que cela fonctionne.

A noté ceci: https://stackoverflow.com/questions/10718668/snapshot-isolation-transaction-aborted-due-to-update-conflict

Et un très bon article: https://sqlperformance.com/2014/06/sql-performance/the-snapshot-isolation-level

Mais ni l'un ni l'autre ne me donne la compréhension que j'aimerais avoir :)

7
Mark

Lors de la suppression de la table parent, SQL Server doit vérifier l'existence de toutes les lignes enfants FK qui font référence à cette ligne. Lorsqu'il n'y a pas d'index enfant approprié, cette vérification effectue une analyse complète de la table enfant:

Full child scan

Si l'analyse rencontre une ligne qui a été modifiée depuis le début de la transaction d'instantané de la commande delete, elle échouera avec un conflit de mise à jour (par définition). Un scan complet touchera évidemment chaque ligne du tableau.

Avec un index approprié, SQL Server peut localiser et tester uniquement les lignes de la table enfant qui peuvent correspondre au parent à supprimer. Lorsque ces lignes particulières n'ont pas été modifiées, aucun conflit de mise à jour ne se produit:

Child seek

Notez que les vérifications de clé étrangère sous les niveaux d'isolement de version de ligne prennent des verrous partagés (pour être correct) ainsi que la détection des conflits de mise à jour. Par exemple, les conseils internes sur les accès aux tables enfants ci-dessus sont:

 PhyOp_Range TBL: [dbo]. [Enfant] 
 Conseils (LIRE-COMMITTEDLOCK FORCEDINDEX DETECT-SNAPSHOT-CONFLICT) 

Malheureusement, cela n'est pas actuellement exposé dans les plans d'exécution.

Articles connexes à moi:

15
Paul White 9

Je suis tombé sur cette réponse d'un gars de Microsoft le n fil posant une question similaire, et je pensais que c'était assez perspicace:

Sans index de prise en charge sur CustomerContactPerson, l'instruction

SUPPRIMER DE ContactPerson WHERE ID = @ID; Nécessite une lecture "actuelle" de toutes les lignes de CustomerContactPerson pour garantir qu'aucune ligne CustomerContactPerson ne fait référence à la ligne ContactPerson supprimée. Avec l'index, DELETE peut déterminer qu'il n'y a pas de lignes liées dans CustomerContactPerson sans lire les lignes affectées par l'autre transaction.

De plus, dans une transaction d'instantané, le modèle de lecture des données que vous allez retourner et mettre à jour consiste à prendre un UPDLOCK lorsque vous lisez. Cela garantit que vous effectuez votre mise à jour sur la base de données "actuelles", et non de données "cohérentes" (instantanées), et que lorsque vous émettez le DML, les données ne seront pas verrouillées et vous ne les écraserez pas sans le vouloir changement d'une autre session.

J'ai reçu la mise à jour de notre équipe de développement. il semble que ma compréhension de la question soit correcte.

Voici leur explication. L'isolement SNAPSHOT garantit que vous verrez une seule version cohérente de la base de données. Lorsque vous lisez la ligne CustomerContactPerson au début de la transaction, vous ne pourrez jamais lire une version ultérieure de la ligne. La suppression sur ContactPerson vous obligerait à lire une version de la ligne CustomerContactPerson plus tard que l'instantané de votre transaction, de sorte que vous obtenez un conflit de mise à jour. Peu importe que vous ne mettiez pas vraiment à jour la ligne CustomerContactPerson, la lire pour valider un FK est traitée de la même manière.

En outre, lorsque l'analyse de la table rencontre l'enregistrement affecté par l'autre transaction, nous pouvons éviter le conflit en verrouillant les lignes que vous souhaitez mettre à jour lorsque vous les lisez.

L'isolement de cliché, en revanche, est vraiment optimiste car les données à modifier ne sont pas réellement verrouillées à l'avance, mais les données sont verrouillées lorsqu'elles sont sélectionnées pour modification. Lorsqu'une ligne de données répond aux critères de mise à jour, la transaction de capture instantanée vérifie que les données n'ont pas été modifiées par une autre transaction après le démarrage de la transaction de capture instantanée. Si les données n'ont pas été modifiées par une autre transaction, la transaction de capture instantanée verrouille les données, met à jour les données, libère le verrou et continue. Si les données ont été modifiées par une autre transaction, un conflit de mise à jour se produit et la transaction d'instantané est annulée.

http://msdn.Microsoft.com/en-us/library/ms345124.aspx

4
Mark

Le message d'erreur a fourni un correctif général et SqlWorldWide a suggéré une réponse à votre problème dans les commentaires ("utilisez plutôt l'isolement sérialisable"). Le problème est votre niveau d'isolement des transactions. Le correctif consiste à modifier le niveau d'isolement de cette transaction. Microsoft explique tout cela dans un article intitulé Leçon 1: Comprendre les niveaux d'isolation de transaction disponibles

Dans l'article, Microsoft déclare sous une section nommée Mettre à jour les conflits:

Il existe un problème de concurrence supplémentaire non encore mentionné car il est spécifique au niveau d'isolement de l'instantané. Si une ligne spécifique (ou version d'une ligne) est lue dans l'isolement de capture instantanée, SQL Server garantit que vous obtiendrez la même ligne si vous émettez la requête plus tard dans la transaction. Que se passe-t-il si la dernière requête est une instruction UPDATE ou DELETE et que la ligne a changé depuis sa première lecture? SQL Server ne peut pas utiliser la version actuelle de la ligne comme base de la mise à jour, car cela romprait la promesse que la ligne ne changerait pas pendant que la transaction de capture instantanée est active. Et il ne peut pas utiliser la version de ligne utilisée par la transaction d'instantané comme base, car l'autre transaction qui a mis à jour ou supprimé la ligne subirait une mise à jour perdue (qui n'est pas autorisée ou prise en charge dans SQL Server). Au lieu de cela, la transaction d'instantané est annulée et elle reçoit le message d'erreur suivant:

Msg 3960, niveau 16, état 4, ligne 1 transaction d'isolement de capture instantanée abandonnée en raison d'un conflit de mise à jour. Vous ne pouvez pas utiliser l'isolement d'instantané pour accéder à la table 'Test.TestTran' directement ou indirectement dans la base de données 'TestDatabase' pour mettre à jour, supprimer ou insérer la ligne qui a été modifiée ou supprimée par une autre transaction. Relancez la transaction ou modifiez le niveau d'isolement pour l'instruction update/delete.

Ceci est similaire à l'erreur que vous recevez.

1

Je spécule, mais je pense que c'est ce qui se passe. Lorsque vous supprimez une ligne parent, le moteur doit appliquer ce que ON DELETE la règle est définie dans la clé étrangère - que vous sachez que vous avez supprimé toutes les lignes enfant, le moteur n'a aucun moyen de le savoir. Comme, comme vous le dites, vous n'avez pas d'index sur la colonne de clé étrangère dans la table enfant (pour des raisons de performances), le moteur a recours à une analyse d'index en cluster (je suppose que vous avez un PK dans la table enfant ) et dès qu'il tombe sur la première ligne périmée, il abandonne la transaction, car il ne peut pas connaître la valeur de clé étrangère insérée en dehors de l'instantané qu'il regarde.

Si vous aviez un index sur la colonne de clé étrangère de la table enfant, le serveur ne pourrait accéder sélectivement qu'aux lignes potentiellement affectées, c'est-à-dire à aucune ligne (puisque vous les avez supprimées d'ici là), évitant ainsi le conflit de cliché - et l'analyse d'index en cluster.

1
mustaccio

Vous devez utiliser l'isolement de l'instantané, pas Lecture de l'instantané validée. Les conflits de mise à jour de cliché se produisent uniquement lors de l'utilisation de l'isolement de cliché et ne pas se produisent lors de l'utilisation de lecture de cliché validée.

Si vous pouvez utiliser Snapshot Read Committed, ce serait une solution très simple à ce problème.

L'isolement de lecture instantanée validée utilise le verrouillage (ET obtient les informations de version de ligne avant chaque instruction), ce qui rend impossible un conflit de mise à jour de capture instantanée.

Les conflits de mise à jour de cliché se produisent dans l'isolement de cliché (pas la lecture de cliché validée) simplement parce que votre transaction, lorsqu'elle tente de valider ses modifications, tente de valider une modification de certaines données dont la version a changé depuis le début de la transaction. Étant donné le scénario que vous avez décrit, il est difficile de comprendre exactement pourquoi vous rencontrez ce problème et il est peut-être lié à une analyse de table par rapport à ce que serait une recherche d'index si vous aviez un index approprié sur votre FK.

Le point principal est que vous devez utiliser l'isolement SNAPSHOT, et non SNAPSHOT READ COMMITTED comme vous l'avez indiqué, et vous pouvez résoudre ce problème en utilisant SNAPSHOT READ COMMITTED.

La seule façon d'obtenir un instantané est de définir le niveau d'isolement sur l'instantané au début de votre transaction. Pour utiliser SNAPSHOT READ COMMITTED, vous devez l'activer dans votre base de données, puis définissez pas le niveau d'isolement dans votre requête ou sproc sur n'importe quoi.

0
user3444696