it-swarm.com.de

Verstehen Sie, warum bei der Indexsperre auf Zeilenebene ein Deadlock aufgetreten ist

Ich habe die folgende Deadlock-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>

Soweit ich verstehen kann, haben wir 3 Sperren auf Zeilenebene für IX_AttributeValueHyperlink_AttributeValueId Index. Ich bin mir nicht sicher, warum einige von ihnen (process330f11fc28) hat eine X-Sperre für diesen Index, die anderen jedoch nicht.

Auch der Ausführungsplan für dieses Löschen sieht so aus

(execution plan

Ich verstehe nicht, warum der Deadlock passiert. Alles scheint in Ordnung zu sein.

Dies ist übrigens in einer SQL Azure-Datenbank, daher wird die RCSI-Isolationsstufe verwendet, jedoch werden unsere Transaktionen eingerichtet (in C# layer), um read commit zu verwenden.

7
Umair

Ich verstehe nicht, warum der Deadlock passiert.

Für diesen Ausführungsplan lautet die Reihenfolge der Sperrvorgänge beim Löschen jeder Zeile wie folgt:

  1. U sperren nicht gruppierten Index (bei der Indexsuche genommen)
  2. U Clustered-Index sperren (vom Löschoperator übernommen)
  3. X Clustered-Index sperren (beim Löschoperator)
  4. X nicht gruppierten Index sperren (beim Löschoperator)

Ich bin nicht sicher warum ... process330f11fc28 hat eine X-Sperre für diesen Index, aber die anderen nicht.

Der Plan hat keine blockierenden Operatoren, daher handelt es sich um eine einfache Pipeline (grob gesagt, jede Zeile erreicht das Ende der Pipeline, bevor die nächste verarbeitet wird).

Als der Deadlock auftrat, hatte ein Prozess (Sitzung 193) eine X Sperre für eine nicht gruppierte Indexzeile (letzter Schritt oben). Die Sitzungen 181 und 201 wurden im ersten Schritt blockiert, wobei versucht wurde, eine inkompatible U -Sperre für dieselbe nicht gruppierte Indexzeilensitzung zu erhalten, die Sitzung 193 ausschließlich gesperrt hat.

Ich entschuldige mich im Voraus, dass die ausführliche Erklärung etwas kompliziert ist.

Interne Update-Sperren

Die Aktualisierungssperre für den nicht gruppierten Index wird automatisch von der Engine übernommen, um eine häufige Art von Konvertierungs-Deadlock zu vermeiden tritt auf, wenn zwei Prozesse eine S -Sperre für dieselbe Ressource erwerben und beide versuchen, in X zu konvertieren. Jeder wird daran gehindert, S in X durch den anderen zu konvertieren, sodass ein Deadlock auftritt.

Die Verwendung einer U -Sperre verhindert dies, da U mit S kompatibel ist, jedoch nicht mit einem anderen U. Natürlich werden S Sperren normalerweise nicht unter RCSI verwendet, aber diese U Sperren sind. Dadurch wird vermieden, dass versucht wird, eine veraltete Version der Zeile zu aktualisieren.

Die automatische U -Sperre wird unter RCSI nur für die Instanz der Tabelle verwendet, die den Zeilenlokator für den Aktualisierungsvorgang bereitstellt. Andere Tabellen in der Abfrage (einschließlich zusätzlicher Verweise auf das Aktualisierungsziel) verwenden weiterhin die Zeilenversionierung.

Diese automatischen U -Sperren haben eine andere Lebensdauer als reguläre Aktualisierungssperren (z. B. mit einem UPDLOCK -Hinweis). Regelmäßige U Sperren werden bis zum Ende der Transaktion gehalten. Interne U Sperren werden bis zum Ende der Anweisung gehalten, mit der Ausnahme: Wenn der gleiche Planoperator, der die Sperre verwendet hat, ableiten kann Damit sich die Zeile nicht für das Update qualifiziert, ist die Sperre sofort freigegeben.

Siehe meinen Artikel Datenänderungen unter Read Committed Snapshot Isolation .

Zyklischer Deadlock

Diese automatische U -Sperre bietet keinen Schutz vor einem zyklischen Deadlock . Zwei Transaktionen, die Ressource A und Ressource B innerhalb einer Transaktion ändern, jedoch in umgekehrter Reihenfolge, sind garantiert blockiert:

  1. Die Transaktion T1 ändert die Zeile R1 (schreibt jedoch nicht fest oder bricht sie ab).
  2. Die Transaktion T2 ändert die Zeile R2 (wird jedoch nicht festgeschrieben oder abgebrochen).
  3. T1 versucht, Zeile R2 und Blöcke zu sperren (T2 hat eine inkompatible Sperre).
  4. T2 versucht, Zeile R1 und Blöcke zu sperren (T1 hat eine inkompatible Sperre).
  5. Sackgasse.

Wo "modifiziert" oben das Einfügen, Aktualisieren, Löschen usw. umfasst.

Spezifischer Deadlock

Das Beispiel in der Frage ist eine Variation dieses Themas, wobei:

  • Sitzung 193 hat die Zeile R1 gelöscht und X in dieser Zeile gehalten
  • Sitzung 193 wartet darauf, U in Zeile R2 zu erhalten
  • Sitzung 181 besitzt eine U Sperre für R2
  • Sitzung 181 wartet darauf, U auf R1 zu erfassen
  • Sackgasse

(Sitzung 201 wartet ebenfalls darauf, U in Zeile R2 zu erhalten, ist jedoch ein unschuldiger Zuschauer.)

Um es klar auszudrücken: Die genaue Deadlock-Sequenz oben kann für den in der Frage gezeigten genauen Ausführungsplan nicht auftreten. Sitzung 181 konnte U auf R2 nicht halten und U auf R1 anfordern, da kein Blockierungsoperator vorhanden war und/oder zwischen Erfassungs- und Freigabepunkten für das nicht gruppierte U lock. Jede von der Indexsuche gefundene gesperrte Zeile U wird garantiert in X konvertiert, bevor die nächste Suchzeile verarbeitet wird.

Nur weil dies jetzt der Plan für die Erklärung ist, heißt das noch lange nicht, dass dies der Plan war, als der Deadlock auftrat. Wenn beispielsweise eine Neukompilierung auf Anweisungsebene erfolgt, kann SQL Server die Kardinalität der Tabellenvariablen erkennen. Dies könnte stattdessen zu einem Hash-Join-Plan führen.

Hash Join Plan

In einem Hash-Join-Plan werden Zeilen aus der Tabellenvariablen verwendet, um eine Hash-Tabelle zu erstellen. Sobald dies abgeschlossen ist, kann SQL Server mit dem Lesen von Zeilen aus AttributeValueHyperlink beginnen und für jede vom Index-Scan ausgegebene Zeile eine U -Sperre aktivieren (es gibt derzeit nichts zu suchen).

Beim Hash-Join wird jede prüfungsseitige Zeile anhand des Join-Prädikats bewertet. Wenn eine Übereinstimmung gefunden wird, wird die Zeile an den Operator Clustered Index Delete weitergeleitet, in dem die gesperrten Sperren U, X und nonclustered X als Teil des Lokalisierens und Löschens verwendet werden die Einträge, die der aktuellen Zeile entsprechen.

Wenn die Zeile jedoch beim Hash-Join nicht beitritt, wird die Sperre U nicht freigegeben. Die U -Sperren für nicht verbundene Zeilen werden weiterhin akkumuliert, bis sie alle freigegeben werden, wenn die aktuelle Anweisung endet. Dies ist einfach eine Folge der U -Sperren, die bei einem Operator ausgeführt werden (nicht gruppierter Index-Scan), aber bei einem anderen Operator (dem Hash-Join) auf Eignung getestet wurden.

Auf jeden Fall ermöglichen mehrere U Sperren den gemeldeten Deadlock.

Vermeidung des Deadlocks

Natürlich kann der einfache Plan für verschachtelte Schleifen auch einen Deadlock verursachen, wenn dieselben Daten verarbeitet werden (die Sperren wären nur offensichtlicher ein zyklischer Deadlock). Um den Deadlock zu vermeiden, müssten Sie sicherstellen, dass die Eingabesätze nicht zusammenhängend sind oder dass die Zeilen in jedem Satz in genau derselben Reihenfolge verarbeitet werden (auf dieselbe Weise sortiert und vom Ausführungsplan in derselben Reihenfolge verarbeitet).

10
Paul White 9

In jeder Sitzung werden mehrere Zeilen aus derselben Tabelle basierend auf dem Wert in einem Sekundärindex gelöscht. Selbst wenn Sie nicht versuchen, dieselbe Zeile in mehreren Sitzungen zu löschen, suchen die Sitzungen anhand des Sekundärindex nach den zu löschenden Zeilen, lesen mit einer U-Sperre und konvertieren sie dann für jede Zeile in eine X-Sperre. Dies schafft die Möglichkeit von Deadlocks.

Sie können dies vermeiden, indem Sie nach den zu löschenden Zeilen ohne U-Sperre suchen und diese dann in einer separaten Anweisung löschen. Einige der Zeilen, die Sie in der ersten Abfrage finden, werden möglicherweise gelöscht, wenn Sie die zweite versuchen. Aber das interessiert dich wahrscheinlich nicht.

Also so etwas wie:

     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)