web-dev-qa-db-fra.com

Aide à comprendre pourquoi un blocage s'est produit sur le verrouillage d'index au niveau de la ligne

J'ai le blocage suivant xml

<deadlock>
  <victim-list>
    <victimProcess id="process3340d548c8" />
  </victim-list>
  <process-list>
    <process id="process3340d548c8" taskpriority="0" logused="1676" waitresource="KEY: 5:72057594083016704 (80e6876e1037)" waittime="4843" ownerId="6974726" transactionname="user_transaction" lasttranstarted="2018-05-25T13:52:16.627" XDES="0x330b1b4458" lockMode="U" schedulerid="1" kpid="34260" status="suspended" spid="201" sbid="2" ecid="0" priority="0" trancount="2" lastbatchstarted="2018-05-25T13:52:16.657" lastbatchcompleted="2018-05-25T13:52:16.657" lastattention="1900-01-01T00:00:00.657" clientapp=".Net SqlClient Data Provider" hostname="RD0003FF430FC8" hostpid="12344" loginname="officearchitect" isolationlevel="read committed (2)" xactid="6974726" currentdb="5" currentdbname="OfficeArchitect_Performance_Test" lockTimeout="4294967295" clientoption1="673185824" clientoption2="128056">
      <executionStack>
        <frame procname="7b7e4b64-e8dd-4a72-8f98-447678798791.model.AttributeValueHyperlink_DeleteByAttributeValueIds" queryhash="0xdc817ac17586cee6" queryplanhash="0x8759f1b16359d45e" line="7" stmtstart="340" stmtend="644" sqlhandle="0x03000500f793ca333699da00eba8000001000000000000000000000000000000000000000000000000000000">
DELETE
        AVH
    FROM
        [model].[AttributeValueHyperlink] AVH
    INNER JOIN
        @AttributeValueIdsTable AVT
    ON
        [AVT].EntityId = [AVH].AttributeValueI    </frame>
        <frame procname="7b7e4b64-e8dd-4a72-8f98-447678798791.model.AttributeValue_DeleteByAttributeValueIds" queryhash="0x0000000000000000" queryplanhash="0x0000000000000000" line="10" stmtstart="490" stmtend="660" sqlhandle="0x030005006cae724fc899da00eba8000001000000000000000000000000000000000000000000000000000000">
EXEC [model].[AttributeValueHyperlink_DeleteByAttributeValueIds]
        @AttributeValueId    </frame>
        <frame procname="7b7e4b64-e8dd-4a72-8f98-447678798791.model.AttributeValue_DeleteByModelItemIds" queryhash="0x0000000000000000" queryplanhash="0x0000000000000000" line="13" stmtstart="732" stmtend="918" sqlhandle="0x03000500def65a51d299da00eba8000001000000000000000000000000000000000000000000000000000000">
EXEC model.AttributeValue_DeleteByAttributeValueIds
        @AttributeValueIds = @AttributeValueId    </frame>
        <frame procname="7b7e4b64-e8dd-4a72-8f98-447678798791.model.ModelItem_Generic_Delete" queryhash="0x0000000000000000" queryplanhash="0x0000000000000000" line="41" stmtstart="2062" stmtend="2208" sqlhandle="0x0300050096f1cb432e9cda00eba8000001000000000000000000000000000000000000000000000000000000">
EXEC [model].[AttributeValue_DeleteByModelItemIds]      
            @ModelItemIdTabl    </frame>
        <frame procname="7b7e4b64-e8dd-4a72-8f98-447678798791.model.ModelItem_Object_Delete" queryhash="0x0000000000000000" queryplanhash="0x0000000000000000" line="25" stmtstart="1088" stmtend="1302" sqlhandle="0x0300050061e52c65069dda00eba8000001000000000000000000000000000000000000000000000000000000">
EXEC [model].[ModelItem_Generic_Delete] 
        @ObjectIdTable,
        @MarkAsDeleted,
        @DeletedBy, 
        @DeletedO    </frame>
      </executionStack>
      <inputbuf>
Proc [Database Id = 5 Object Id = 1697441121]   </inputbuf>
    </process>
    <process id="process3330f29088" taskpriority="0" logused="1744" waitresource="KEY: 5:72057594083016704 (5b32eda0fe69)" waittime="4575" ownerId="6948390" transactionname="user_transaction" lasttranstarted="2018-05-25T13:52:14.370" XDES="0x331cb2c458" lockMode="U" schedulerid="2" kpid="29596" status="suspended" spid="181" sbid="2" ecid="0" priority="0" trancount="2" lastbatchstarted="2018-05-25T13:52:14.403" lastbatchcompleted="2018-05-25T13:52:14.390" lastattention="1900-01-01T00:00:00.390" clientapp=".Net SqlClient Data Provider" hostname="RD0003FF430FC8" hostpid="12344" loginname="officearchitect" isolationlevel="read committed (2)" xactid="6948390" currentdb="5" currentdbname="OfficeArchitect_Performance_Test" lockTimeout="4294967295" clientoption1="673185824" clientoption2="128056">
      <executionStack>
        <frame procname="7b7e4b64-e8dd-4a72-8f98-447678798791.model.AttributeValueHyperlink_DeleteByAttributeValueIds" queryhash="0xdc817ac17586cee6" queryplanhash="0x8759f1b16359d45e" line="7" stmtstart="340" stmtend="644" sqlhandle="0x03000500f793ca333699da00eba8000001000000000000000000000000000000000000000000000000000000">
DELETE
        AVH
    FROM
        [model].[AttributeValueHyperlink] AVH
    INNER JOIN
        @AttributeValueIdsTable AVT
    ON
        [AVT].EntityId = [AVH].AttributeValueI    </frame>
        <frame procname="7b7e4b64-e8dd-4a72-8f98-447678798791.model.AttributeValue_DeleteByAttributeValueIds" queryhash="0x0000000000000000" queryplanhash="0x0000000000000000" line="10" stmtstart="490" stmtend="660" sqlhandle="0x030005006cae724fc899da00eba8000001000000000000000000000000000000000000000000000000000000">
EXEC [model].[AttributeValueHyperlink_DeleteByAttributeValueIds]
        @AttributeValueId    </frame>
        <frame procname="7b7e4b64-e8dd-4a72-8f98-447678798791.model.AttributeValue_DeleteByModelItemIds" queryhash="0x0000000000000000" queryplanhash="0x0000000000000000" line="13" stmtstart="732" stmtend="918" sqlhandle="0x03000500def65a51d299da00eba8000001000000000000000000000000000000000000000000000000000000">
EXEC model.AttributeValue_DeleteByAttributeValueIds
        @AttributeValueIds = @AttributeValueId    </frame>
        <frame procname="7b7e4b64-e8dd-4a72-8f98-447678798791.model.ModelItem_Generic_Delete" queryhash="0x0000000000000000" queryplanhash="0x0000000000000000" line="41" stmtstart="2062" stmtend="2208" sqlhandle="0x0300050096f1cb432e9cda00eba8000001000000000000000000000000000000000000000000000000000000">
EXEC [model].[AttributeValue_DeleteByModelItemIds]      
            @ModelItemIdTabl    </frame>
        <frame procname="7b7e4b64-e8dd-4a72-8f98-447678798791.model.ModelItem_Relationship_Delete" queryhash="0x0000000000000000" queryplanhash="0x0000000000000000" line="23" stmtstart="1078" stmtend="1302" sqlhandle="0x030005000d989e704f9dda00eba8000001000000000000000000000000000000000000000000000000000000">
EXEC [model].[ModelItem_Generic_Delete]
        @RelationshipIdTable,
        @MarkAsDeleted,
        @DeletedBy, 
        @DeletedO    </frame>
      </executionStack>
      <inputbuf>
Proc [Database Id = 5 Object Id = 1889441805]   </inputbuf>
    </process>
    <process id="process330f11fc28" taskpriority="0" logused="32948" waitresource="KEY: 5:72057594083016704 (80e6876e1037)" waittime="2558" ownerId="6941127" transactionname="user_transaction" lasttranstarted="2018-05-25T13:52:13.970" XDES="0x33199a4458" lockMode="U" schedulerid="2" kpid="91236" status="suspended" spid="193" sbid="2" ecid="0" priority="0" trancount="2" lastbatchstarted="2018-05-25T13:52:21.987" lastbatchcompleted="2018-05-25T13:52:21.983" lastattention="1900-01-01T00:00:00.983" clientapp=".Net SqlClient Data Provider" hostname="RD0003FF430FC8" hostpid="12344" loginname="officearchitect" isolationlevel="read committed (2)" xactid="6941127" currentdb="5" currentdbname="OfficeArchitect_Performance_Test" lockTimeout="4294967295" clientoption1="673185824" clientoption2="128056">
      <executionStack>
        <frame procname="7b7e4b64-e8dd-4a72-8f98-447678798791.model.AttributeValueHyperlink_DeleteByAttributeValueIds" queryhash="0xdc817ac17586cee6" queryplanhash="0x8759f1b16359d45e" line="7" stmtstart="340" stmtend="644" sqlhandle="0x03000500f793ca333699da00eba8000001000000000000000000000000000000000000000000000000000000">
DELETE
        AVH
    FROM
        [model].[AttributeValueHyperlink] AVH
    INNER JOIN
        @AttributeValueIdsTable AVT
    ON
        [AVT].EntityId = [AVH].AttributeValueI    </frame>
        <frame procname="7b7e4b64-e8dd-4a72-8f98-447678798791.model.AttributeValue_DeleteByAttributeValueIds" queryhash="0x0000000000000000" queryplanhash="0x0000000000000000" line="10" stmtstart="490" stmtend="660" sqlhandle="0x030005006cae724fc899da00eba8000001000000000000000000000000000000000000000000000000000000">
EXEC [model].[AttributeValueHyperlink_DeleteByAttributeValueIds]
        @AttributeValueId    </frame>
        <frame procname="7b7e4b64-e8dd-4a72-8f98-447678798791.model.AttributeValue_DeleteByModelItemIds" queryhash="0x0000000000000000" queryplanhash="0x0000000000000000" line="13" stmtstart="732" stmtend="918" sqlhandle="0x03000500def65a51d299da00eba8000001000000000000000000000000000000000000000000000000000000">
EXEC model.AttributeValue_DeleteByAttributeValueIds
        @AttributeValueIds = @AttributeValueId    </frame>
        <frame procname="7b7e4b64-e8dd-4a72-8f98-447678798791.model.ModelItem_Generic_Delete" queryhash="0x0000000000000000" queryplanhash="0x0000000000000000" line="41" stmtstart="2062" stmtend="2208" sqlhandle="0x0300050096f1cb432e9cda00eba8000001000000000000000000000000000000000000000000000000000000">
EXEC [model].[AttributeValue_DeleteByModelItemIds]      
            @ModelItemIdTabl    </frame>
        <frame procname="7b7e4b64-e8dd-4a72-8f98-447678798791.model.ModelItem_Relationship_Delete" queryhash="0x0000000000000000" queryplanhash="0x0000000000000000" line="23" stmtstart="1078" stmtend="1302" sqlhandle="0x030005000d989e704f9dda00eba8000001000000000000000000000000000000000000000000000000000000">
EXEC [model].[ModelItem_Generic_Delete]
        @RelationshipIdTable,
        @MarkAsDeleted,
        @DeletedBy, 
        @DeletedO    </frame>
        <frame procname="7b7e4b64-e8dd-4a72-8f98-447678798791.model.RelationshipPair_DeleteByModelItemIds" queryhash="0x0000000000000000" queryplanhash="0x0000000000000000" line="21" stmtstart="1252" stmtend="1462" sqlhandle="0x03000500bc1cd015159eda00eba8000001000000000000000000000000000000000000000000000000000000">
EXEC [model].[ModelItem_Relationship_Delete]
        @RelationshipIds,
        0,
        @DeletedBy,
        @DeletedOn,    </frame>
        <frame procname="7b7e4b64-e8dd-4a72-8f98-447678798791.model.ModelItem_Object_Delete" queryhash="0x0000000000000000" queryplanhash="0x0000000000000000" line="20" stmtstart="878" stmtend="1076" sqlhandle="0x0300050061e52c65069dda00eba8000001000000000000000000000000000000000000000000000000000000">
EXEC [model].[RelationshipPair_DeleteByModelItemIds]
        @ObjectIdTable,
        @DeletedBy,
        @DeletedO    </frame>
      </executionStack>
      <inputbuf>
Proc [Database Id = 5 Object Id = 1697441121]   </inputbuf>
    </process>
  </process-list>
  <resource-list>
    <keylock hobtid="72057594083016704" dbid="5" objectname="7b7e4b64-e8dd-4a72-8f98-447678798791.model.AttributeValueHyperlink" indexname="IX_AttributeValueHyperlink_AttributeValueId" id="lock3320f42880" mode="U" associatedObjectId="72057594083016704">
      <owner-list>
        <owner id="process3330f29088" mode="U" />
      </owner-list>
      <waiter-list>
        <waiter id="process3340d548c8" mode="U" requestType="wait" />
      </waiter-list>
    </keylock>
    <keylock hobtid="72057594083016704" dbid="5" objectname="7b7e4b64-e8dd-4a72-8f98-447678798791.model.AttributeValueHyperlink" indexname="IX_AttributeValueHyperlink_AttributeValueId" id="lock3320f4fb00" mode="X" associatedObjectId="72057594083016704">
      <owner-list>
        <owner id="process330f11fc28" mode="X" />
      </owner-list>
      <waiter-list>
        <waiter id="process3330f29088" mode="U" requestType="wait" />
      </waiter-list>
    </keylock>
    <keylock hobtid="72057594083016704" dbid="5" objectname="7b7e4b64-e8dd-4a72-8f98-447678798791.model.AttributeValueHyperlink" indexname="IX_AttributeValueHyperlink_AttributeValueId" id="lock3320f42880" mode="U" associatedObjectId="72057594083016704">
      <owner-list>
        <owner id="process3340d548c8" mode="U" requestType="wait" />
      </owner-list>
      <waiter-list>
        <waiter id="process330f11fc28" mode="U" requestType="wait" />
      </waiter-list>
    </keylock>
  </resource-list>
</deadlock>

Pour autant que je puisse comprendre, nous avons 3 verrous de niveau sur le IX_AttributeValueHyperlink_AttributeValueId index. Je ne sais pas pourquoi certains d'entre eux (process330f11fc28) a un X-lock sur cet index, mais pas les autres.

Le plan d'exécution de cette suppression ressemble également à ceci

execution plan

Je ne comprends pas pourquoi l'impasse se produit. Tout semble ok.

Il s'agit d'une base de données Azure SQL d'ailleurs, il utilise donc le niveau d'isolement RCSI, mais nos transactions sont configurées (dans le C# couche) pour utiliser la lecture validée.

7
Umair

Je ne comprends pas pourquoi l'impasse se produit.

Pour ce plan d'exécution, la séquence d'opérations de verrouillage impliquées dans la suppression de chaque ligne est:

  1. U verrouiller l'index non cluster (pris lors de la recherche d'index)
  2. U verrouiller l'index clusterisé (pris à l'opérateur de suppression)
  3. X verrouiller l'index clusterisé (à l'opérateur de suppression)
  4. X verrouille l'index non clusterisé (à l'opérateur de suppression)

Je ne sais pas pourquoi ... process330f11fc28 a un verrou X sur cet index, mais les autres n'en ont pas.

Le plan n'a pas d'opérateurs de blocage, il s'agit donc d'un simple pipeline (en gros, chaque ligne arrive à la fin du pipeline avant que la suivante ne soit traitée).

Lorsque le blocage s'est produit, un processus (session 193) avait un verrou X sur une ligne d'index non clusterisée (dernière étape ci-dessus). Les sessions 181 et 201 ont été bloquées à la première étape, en essayant d'obtenir un verrou U incompatible sur la même ligne d'index non cluster que la session 193 a verrouillée exclusivement.

Je m'excuse à l'avance que l'explication détaillée est quelque peu impliquée.

Verrous de mise à jour internes

Le verrou de mise à jour sur l'index non clusterisé est pris automatiquement par le moteur pour éviter un type commun de blocage de conversion , qui se produit lorsque deux processus acquièrent un verrou S sur la même ressource, puis tentent tous les deux de se convertir en X. Chacun ne peut pas convertir S en X par l'autre, donc un blocage se produit.

Prendre un verrou U empêche cela car U est compatible avec S mais pas avec un autre U. Naturellement, les verrous S ne sont pas normalement pris sous RCSI, mais ces verrous U le sont. Cela évite de tenter de mettre à jour une version périmée de la ligne.

Le verrou automatique U est pris sous RCSI uniquement pour l'instance de la table qui fournit le localisateur de ligne pour l'opération de mise à jour. Les autres tables de la requête (y compris les références supplémentaires à la cible de mise à jour) continuent d'utiliser le versionnement des lignes.

Ces verrous automatiques U ont une durée de vie différente des verrous de mise à jour standard (tels que ceux pouvant être pris avec un indice UPDLOCK). Des verrous U réguliers sont maintenus jusqu'à la fin de la transaction. Les verrous internes U sont conservés à la fin de la instruction, à une exception près: si le même opérateur de plan qui a pris le verrou peut en déduire que la ligne ne n'est pas admissible à la mise à jour, le verrou est libéré immédiatement.

Voir mon article Modifications des données sous Lire l'isolement de l'instantané validé .

Blocage cyclique

Ce verrouillage automatique U ne fournit pas de protection contre un blocage cyclique . Deux transactions qui modifient la ressource A et la ressource B à l'intérieur d'une transaction, mais dans l'ordre inverse, sont garanties de bloquer:

  1. La transaction T1 modifie la ligne R1 (mais ne valide ni n'abandonne).
  2. La transaction T2 modifie la ligne R2 (mais ne valide ni n'abandonne).
  3. T1 tente de verrouiller la ligne R2 et les blocs (T2 a un verrou incompatible).
  4. T2 tente de verrouiller la ligne R1 et les blocs (T1 a un verrou incompatible).
  5. Impasse.

Lorsque "modifie" ci-dessus comprend l'insertion, la mise à jour, la suppression, etc.

Blocage spécifique

L'exemple de la question est une variation sur ce thème, où:

  • La session 193 a supprimé la ligne R1, contenant X sur cette ligne
  • La session 193 attend pour acquérir U sur la ligne R2
  • La session 181 possède un verrou U sur R2
  • La session 181 attend pour acquérir U sur R1
  • Impasse

(la session 201 attend également d'acquérir U sur la ligne R2 mais c'est un spectateur innocent.)

Pour être clair: la séquence d'interblocage exacte ci-dessus ne peut pas se produire pour le plan d'exécution précis indiqué dans la question. La session 181 n'a pas pu contenir U sur R2 et continuer à demander U sur R1 en raison de l'absence d'un opérateur de blocage et/ou de la séparation entre les points d'acquisition et de libération pour les non-cluster U. Toute ligne verrouillée U trouvée par la recherche d'index est garantie d'être convertie en X avant le traitement de la ligne de recherche suivante.

Néanmoins, ce n'est pas parce que tel est le plan de la déclaration maintenant que c'était le plan lorsque l'impasse s'est produite. Par exemple, lorsqu'une recompilation au niveau de l'instruction se produit, SQL Server peut voir la cardinalité de la variable de table. Cela pourrait bien conduire à un plan de jointure de hachage à la place.

Plan de jointure Hash

Dans un plan de jointure de hachage, les lignes de la variable de table seraient utilisées pour créer une table de hachage. Une fois cette opération terminée, SQL Server peut commencer à lire des lignes à partir de AttributeValueHyperlink, en prenant un verrou U sur chaque ligne émise par l'analyse d'index (il n'y a plus rien à rechercher maintenant).

Au niveau de la jointure par hachage, chaque ligne côté sonde est évaluée par rapport au prédicat de jointure. Si une correspondance est trouvée, la ligne passe à l'opérateur de suppression d'index en cluster, où les verrous en cluster U, X et non clusterisé X sont pris dans le cadre de la localisation et de la suppression. les entrées correspondant à la ligne courante.

Cependant, si la ligne ne se joint pas à la jointure de hachage, le verrou U n'est pas libéré. Les verrous U pour les lignes non jointes continueront de s'accumuler jusqu'à ce qu'ils soient tous libérés à la fin de l'instruction en cours. Ceci est simplement une conséquence des verrous U pris par un opérateur (scan d'index non cluster) mais testés pour l'éligibilité par un autre (la jointure de hachage).

Quoi qu'il en soit, plusieurs verrous U rendent possible le blocage signalé.

Éviter l'impasse

Bien sûr, le simple plan de boucles imbriquées peut également entraîner un blocage lors du traitement des mêmes données (les verrous seraient simplement un blocage cyclique). Pour éviter l'impasse, vous devez vous assurer que les ensembles d'entrée sont disjoints ou que les lignes de chaque ensemble sont traitées strictement dans le même ordre (triées de la même manière et traitées dans la même séquence par le plan d'exécution).

10
Paul White 9

Chaque session supprime plusieurs lignes de la même table en fonction de la valeur d'un index secondaire. Même si vous n'essayez pas de supprimer la même ligne dans plusieurs sessions, les sessions rechercheront les lignes à supprimer à l'aide de l'index secondaire, lisant avec un verrou U, puis les convertiront en verrou X pour chaque ligne. Cela crée la possibilité de blocages.

Vous pouvez éviter cela en recherchant les lignes à supprimer sans verrou U, puis en les supprimant dans une instruction distincte. Certaines des lignes que vous trouvez dans la première requête peuvent être supprimées au moment où vous essayez la seconde. Mais vous ne vous en souciez probablement pas.

Donc quelque chose comme:

     declare @ids_to_delete table(id int)

     insert into @ids_to_delete(id)
     select id
     from [model].[AttributeValueHyperlink] AVH
     INNER JOIN @AttributeValueIdsTable AVT
     ON
        [AVT].EntityId = [AVH].AttributeValueI 

     delete from  [model].[AttributeValueHyperlink] 
     where id in (select id from @ids_to_delete)