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


7

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 den IX_AttributeValueHyperlink_AttributeValueIdIndex. Ich bin nicht sicher, warum einige von ihnen ( process330f11fc28) eine X-Sperre für diesen Index haben, die anderen jedoch nicht.

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

Ausführungsplan

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

Dies befindet sich übrigens in einer SQL Azure-Datenbank, daher wird die RCSI-Isolationsstufe verwendet. Unsere Transaktionen sind jedoch (in der C#Ebene) so eingerichtet, dass sie Lese- Commit verwenden.

Antworten:


10

Ich verstehe nicht, warum der Deadlock passiert.

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

  1. U Nicht gruppierten Index sperren (bei der Indexsuche übernommen)
  2. U Clustered-Index sperren (beim 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 (ungefähr gesagt, jede Zeile erreicht das Ende der Pipeline, bevor die nächste verarbeitet wird).

Als der Deadlock auftrat, hatte ein Prozess (Sitzung 193) eine XSperre für eine nicht gruppierte Indexzeile (letzter Schritt oben). Die Sitzungen 181 und 201 wurden im ersten Schritt blockiert, wobei versucht wurde, eine inkompatible USperre 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 einen allgemeinen Typ eines Konvertierungs-Deadlocks zu vermeiden , der auftritt, wenn zwei Prozesse eine SSperre für dieselbe Ressource erwerben und dann beide versuchen, eine Konvertierung durchzuführenX . Jeder wird aus der Umwandlung verhindert Szu Xden anderen, so ein Deadlock auftritt.

Das Aufnehmen eines USchlosses verhindert dies, da Ues mit Seinem anderen kompatibel ist, aber nicht U. Natürlich werden SSchlösser normalerweise nicht unter RCSI genommen, aber diese USchlösser sind es. Dadurch wird vermieden, dass versucht wird, eine veraltete Version der Zeile zu aktualisieren.

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

Diese automatischen USperren haben eine andere Lebensdauer als reguläre Aktualisierungssperren (z. B. mit einem UPDLOCKHinweis). Regelmäßige USperren werden bis zum Ende der Transaktion gehalten . Interne USperren werden bis zum Ende der Anweisung gehalten , mit einer Ausnahme: Wenn derselbe Planoperator, der die Sperre vorgenommen hat, daraus schließen kann, dass die Zeile nicht für die Aktualisierung qualifiziert ist, wird die Sperre sofort aufgehoben .

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

Zyklischer Deadlock

Diese automatische UVerriegelung 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:

  • Session hat 193 Zeilen R1 gelöscht, das Halten Xauf dieser Zeile
  • Die Sitzung 193 wartet darauf, Uin Zeile R2 erfasst zu werden
  • Sitzung 181 besitzt eine USperre für R2
  • Sitzung 181 wartet Uauf R1
  • Sackgasse

(Sitzung 201 wartet ebenfalls auf den Erwerb Uin Zeile R2, ist jedoch ein unschuldiger Zuschauer.)

Um es klar auszudrücken: Die oben angegebene genaue Deadlock-Sequenz kann für den in der Frage gezeigten genauen Ausführungsplan nicht auftreten. Die Sitzung 181 konnte UR2 nicht halten und UR1 aufgrund des Fehlens eines blockierenden Operators und / oder der Trennung zwischen Erfassungs- und Freigabepunkten für die nicht gruppierte USperre anfordern . Jede Ugesperrte Zeile, die von der Indexsuche gefunden wird, wird garantiert konvertiert, Xbevor 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 beginnen AttributeValueHyperlinkund Ujede vom Index-Scan ausgegebene Zeile sperren (es gibt jetzt nichts zu suchen).

Beim Hash-Join wird jede prüfungsseitige Zeile anhand des Join-Prädikats bewertet. Wenn eine Übereinstimmung der Zeile gefunden wird geht auf den Clustered Index Delete - Operator, wo die gruppierten U, Xund nicht gruppierten XSperren werden als Teil genommen der Lokalisierung und die Einträge zu löschen , um die aktuelle Zeile entspricht.

Wenn jedoch die Zeile hat nicht an der Hash - Join verbinden, die Usich Schloss nicht freigegeben wird . Die USperren für nicht verbundene Zeilen werden so lange akkumuliert, bis sie alle freigegeben werden, wenn die aktuelle Anweisung endet. Dies ist einfach eine Folge der USperren, die bei einem Operator vorgenommen wurden (nicht gruppierter Index-Scan), bei einem anderen jedoch auf Eignung getestet wurden (Hash-Join).

Auf jeden UFall ermöglichen mehrere Sperren den gemeldeten Deadlock.

Deadlock vermeiden

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).


1

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)

Ich habe es versucht. Leider hat es das Thema nur nach oben getrieben. Ich habe jetzt einen Deadlock auf Seitenebene für die Tabelle selbst (oder genauer den Clustered-Index).
Umair

Nun, Sie können immer einen TABLOCK-Hinweis verwenden, um sicherzustellen, dass die DELETEs jeweils eine Sitzung ausführen.
David Browne - Microsoft

Aha, ich verstehe. Wann würde das TABLOCKveröffentlicht werden? Am Ende der Löschanweisung oder am Ende der Transaktion?
Umair
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.