SQL Deadlock auf demselben exklusiv gesperrten Clustered Key (mit NHibernate) beim Löschen / Einfügen


29

Ich arbeite jetzt schon seit einigen Tagen an diesem Deadlock-Problem und egal was ich tue, es bleibt auf die eine oder andere Weise bestehen.

Zunächst die allgemeine Prämisse: Wir haben Besuche mit VisitItems in einer Eins-zu-Viele-Beziehung.

Relevante Informationen zu VisitItems:

CREATE TABLE [BAR].[VisitItems] (
    [Id]                INT             IDENTITY (1, 1) NOT NULL,
    [VisitType]         INT             NOT NULL,
    [FeeRateType]       INT             NOT NULL,
    [Amount]            DECIMAL (18, 2) NOT NULL,
    [GST]               DECIMAL (18, 2) NOT NULL,
    [Quantity]          INT             NOT NULL,
    [Total]             DECIMAL (18, 2) NOT NULL,
    [ServiceFeeType]    INT   NOT NULL,
    [ServiceText]       NVARCHAR (200)  NULL,
    [InvoicingProviderId] INT   NULL,
    [FeeItemId]        INT             NOT NULL,
    [VisitId]          INT             NULL,
    [IsDefault] BIT NOT NULL DEFAULT 0, 
    [SourceVisitItemId] INT NULL, 
    [OverrideCode] INT NOT NULL DEFAULT 0, 
    [InvoiceToCentre] BIT NOT NULL DEFAULT 0, 
    [IsSurchargeItem] BIT NOT NULL DEFAULT 0, 
    CONSTRAINT [PK_BAR.VisitItems] PRIMARY KEY CLUSTERED ([Id] ASC),
    CONSTRAINT [FK_BAR.VisitItems_BAR.FeeItems_FeeItem_Id] FOREIGN KEY ([FeeItemId]) REFERENCES [BAR].[FeeItems] ([Id]),
    CONSTRAINT [FK_BAR.VisitItems_BAR.Visits_Visit_Id] FOREIGN KEY ([VisitId]) REFERENCES [BAR].[Visits] ([Id]), 
    CONSTRAINT [FK_BAR.VisitItems_BAR.VisitTypes] FOREIGN KEY ([VisitType]) REFERENCES [BAR].[VisitTypes]([Id]), 
    CONSTRAINT [FK_BAR.VisitItems_BAR.FeeRateTypes] FOREIGN KEY ([FeeRateType]) REFERENCES [BAR].[FeeRateTypes]([Id]),
    CONSTRAINT [FK_BAR.VisitItems_CMN.Users_Id] FOREIGN KEY (InvoicingProviderId) REFERENCES [CMN].[Users] ([Id]),
    CONSTRAINT [FK_BAR.VisitItems_BAR.VisitItems_SourceVisitItem_Id] FOREIGN KEY ([SourceVisitItemId]) REFERENCES [BAR].[VisitItems]([Id]),
    CONSTRAINT [CK_SourceVisitItemId_Not_Equal_Id] CHECK ([SourceVisitItemId] <> [Id]),
    CONSTRAINT [FK_BAR.VisitItems_BAR.OverrideCodes] FOREIGN KEY ([OverrideCode]) REFERENCES [BAR].[OverrideCodes]([Id]),
    CONSTRAINT [FK_BAR.VisitItems_BAR.ServiceFeeTypes] FOREIGN KEY ([ServiceFeeType]) REFERENCES [BAR].[ServiceFeeTypes]([Id])
)

CREATE NONCLUSTERED INDEX [IX_FeeItem_Id]
    ON [BAR].[VisitItems]([FeeItemId] ASC)

CREATE NONCLUSTERED INDEX [IX_Visit_Id]
    ON [BAR].[VisitItems]([VisitId] ASC)

Besuchsinfo:

CREATE TABLE [BAR].[Visits] (
    [Id]                     INT            IDENTITY (1, 1) NOT NULL,
    [VisitType]              INT            NOT NULL,
    [DateOfService]          DATETIMEOFFSET  NOT NULL,
    [InvoiceAnnotation]      NVARCHAR(255)  NULL ,
    [PatientId]              INT            NOT NULL,
    [UserId]                 INT            NULL,
    [WorkAreaId]             INT            NOT NULL, 
    [DefaultItemOverride] BIT NOT NULL DEFAULT 0, 
    [DidNotWaitAdjustmentId] INT NULL, 
    [AppointmentId] INT NULL, 
    CONSTRAINT [PK_BAR.Visits] PRIMARY KEY CLUSTERED ([Id] ASC),
    CONSTRAINT [FK_BAR.Visits_CMN.Patients] FOREIGN KEY ([PatientId]) REFERENCES [CMN].[Patients] ([Id]) ON DELETE CASCADE,
    CONSTRAINT [FK_BAR.Visits_CMN.Users] FOREIGN KEY ([UserId]) REFERENCES [CMN].[Users] ([Id]),
    CONSTRAINT [FK_BAR.Visits_CMN.WorkAreas_WorkAreaId] FOREIGN KEY ([WorkAreaId]) REFERENCES [CMN].[WorkAreas] ([Id]), 
    CONSTRAINT [FK_BAR.Visits_BAR.VisitTypes] FOREIGN KEY ([VisitType]) REFERENCES [BAR].[VisitTypes]([Id]),
    CONSTRAINT [FK_BAR.Visits_BAR.Adjustments] FOREIGN KEY ([DidNotWaitAdjustmentId]) REFERENCES [BAR].[Adjustments]([Id]), 
);

CREATE NONCLUSTERED INDEX [IX_Visits_PatientId]
    ON [BAR].[Visits]([PatientId] ASC);

CREATE NONCLUSTERED INDEX [IX_Visits_UserId]
    ON [BAR].[Visits]([UserId] ASC);

CREATE NONCLUSTERED INDEX [IX_Visits_WorkAreaId]
    ON [BAR].[Visits]([WorkAreaId]);

Mehrere Benutzer möchten die VisitItems-Tabelle auf folgende Weise gleichzeitig aktualisieren:

Eine separate Webanforderung erstellt einen Besuch mit VisitItems (normalerweise 1). Dann (die Problemanfrage):

  1. Die Webanforderung geht ein, öffnet die NHibernate-Sitzung und startet die NHibernate-Transaktion (mit Repeatable Read und READ_COMMITTED_SNAPSHOT).
  2. Lesen Sie alle Besuchselemente für einen bestimmten Besuch von VisitId .
  3. Der Code prüft, ob die Elemente noch relevant sind oder ob wir neue Elemente mit komplexen Regeln benötigen (so etwas lange, z. B. 40 ms).
  4. Der Code findet, dass 1 Element hinzugefügt werden muss, und fügt es mit NHibernate Visit.VisitItems.Add (..) hinzu.
  5. Der Code gibt an, dass ein Element gelöscht werden muss (nicht das gerade hinzugefügte), und entfernt es mithilfe von NHibernate Visit.VisitItems.Remove (Element).
  6. Code schreibt die Transaktion fest

Mit einem Tool simuliere ich 12 gleichzeitige Anforderungen, was in einer zukünftigen Produktionsumgebung sehr wahrscheinlich ist.

[BEARBEITEN] Auf Anfrage wurden viele der Untersuchungsdetails entfernt, die ich hier hinzugefügt hatte, um sie kurz zu halten.

Nach vielen Recherchen war der nächste Schritt zu überlegen, wie ich einen Hinweis auf einen anderen Index als den in der where-Klausel verwendeten (dh den Primärschlüssel, da dieser zum Löschen verwendet wird) sperren kann, sodass ich meine lock-Anweisung auf änderte :

var items = (List<VisitItem>)_session.CreateSQLQuery(@"SELECT * FROM BAR.VisitItems WITH (XLOCK, INDEX([PK_BAR.VisitItems]))
        WHERE VisitId = :visitId")
        .AddEntity(typeof(VisitItem))
        .SetParameter("visitId", qi.Visit.Id)
        .List<VisitItem>();

Dies reduzierte die Deadlocks in der Frequenz leicht, aber sie passierten immer noch. Und hier fange ich an, mich zu verlaufen:

Drei exklusive Schlösser?

<deadlock-list>
  <deadlock victim="process3f71e64e8">
    <process-list>
      <process id="process3f71e64e8" taskpriority="0" logused="0" waitresource="KEY: 5:72057594071744512 (a5e1814e40ba)" waittime="3812" ownerId="8004520" transactionname="user_transaction" lasttranstarted="2015-12-14T10:24:58.010" XDES="0x3f7cb43b0" lockMode="X" schedulerid="1" kpid="15788" status="suspended" spid="63" sbid="0" ecid="0" priority="0" trancount="1" lastbatchstarted="2015-12-14T10:24:58.013" lastbatchcompleted="2015-12-14T10:24:58.013" lastattention="1900-01-01T00:00:00.013" clientapp=".Net SqlClient Data Provider" hostname="ABC" hostpid="10016" loginname="bsapp" isolationlevel="repeatable read (3)" xactid="8004520" currentdb="5" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
        <executionStack>
          <frame procname="adhoc" line="1" stmtstart="18" stmtend="254" sqlhandle="0x0200000024a9e43033ef90bb631938f939038627209baafb0000000000000000000000000000000000000000">
            unknown
          </frame>
          <frame procname="unknown" line="1" sqlhandle="0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000">
            unknown
          </frame>
        </executionStack>
        <inputbuf>
          (@p0 int)SELECT * FROM BAR.VisitItems WITH (XLOCK, INDEX([PK_BAR.VisitItems]))
          WHERE VisitId = @p0
        </inputbuf>
      </process>
      <process id="process4105af468" taskpriority="0" logused="1824" waitresource="KEY: 5:72057594071744512 (8194443284a0)" waittime="3792" ownerId="8004519" transactionname="user_transaction" lasttranstarted="2015-12-14T10:24:58.010" XDES="0x3f02ea3b0" lockMode="S" schedulerid="8" kpid="15116" status="suspended" spid="65" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2015-12-14T10:24:58.033" lastbatchcompleted="2015-12-14T10:24:58.033" lastattention="1900-01-01T00:00:00.033" clientapp=".Net SqlClient Data Provider" hostname="ABC" hostpid="10016" loginname="bsapp" isolationlevel="repeatable read (3)" xactid="8004519" currentdb="5" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
        <executionStack>
          <frame procname="adhoc" line="1" stmtstart="18" stmtend="98" sqlhandle="0x0200000075abb0074bade5aa57b8357410941428df4d54130000000000000000000000000000000000000000">
            unknown
          </frame>
          <frame procname="unknown" line="1" sqlhandle="0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000">
            unknown
          </frame>
        </executionStack>
        <inputbuf>
          (@p0 int)DELETE FROM BAR.VisitItems WHERE Id = @p0
        </inputbuf>
      </process>
    </process-list>
    <resource-list>
      <keylock hobtid="72057594071744512" dbid="5" objectname="BAR.VisitItems" indexname="PK_BAR.VisitItems" id="lock449e27500" mode="X" associatedObjectId="72057594071744512">
        <owner-list>
          <owner id="process4105af468" mode="X"/>
        </owner-list>
        <waiter-list>
          <waiter id="process3f71e64e8" mode="X" requestType="wait"/>
        </waiter-list>
      </keylock>
      <keylock hobtid="72057594071744512" dbid="5" objectname="BAR.VisitItems" indexname="PK_BAR.VisitItems" id="lock46a525080" mode="X" associatedObjectId="72057594071744512">
        <owner-list>
          <owner id="process3f71e64e8" mode="X"/>
        </owner-list>
        <waiter-list>
          <waiter id="process4105af468" mode="S" requestType="wait"/>
        </waiter-list>
      </keylock>
    </resource-list>
  </deadlock>
</deadlock-list>

Ein Trace der resultierenden Anzahl von Abfragen sieht folgendermaßen aus.
[BEARBEITEN] Whoa. Was für eine Woche. Ich habe jetzt die Ablaufverfolgung mit der unreduzierten Ablaufverfolgung der relevanten Anweisung aktualisiert, von der ich denke, dass sie zum Deadlock führt.

exec sp_executesql N'SELECT * FROM BAR.VisitItems WITH (XLOCK, INDEX([PK_BAR.VisitItems]))
                WHERE VisitId = @p0',N'@p0 int',@p0=3826
go
exec sp_executesql N'SELECT visititems0_.VisitId as VisitId1_, visititems0_.Id as Id1_, visititems0_.Id as Id37_0_, visititems0_.VisitType as VisitType37_0_, visititems0_.FeeItemId as FeeItemId37_0_, visititems0_.FeeRateType as FeeRateT4_37_0_, visititems0_.Amount as Amount37_0_, visititems0_.GST as GST37_0_, visititems0_.Quantity as Quantity37_0_, visititems0_.Total as Total37_0_, visititems0_.ServiceFeeType as ServiceF9_37_0_, visititems0_.ServiceText as Service10_37_0_, visititems0_.InvoiceToCentre as Invoice11_37_0_, visititems0_.IsDefault as IsDefault37_0_, visititems0_.OverrideCode as Overrid13_37_0_, visititems0_.IsSurchargeItem as IsSurch14_37_0_, visititems0_.VisitId as VisitId37_0_, visititems0_.InvoicingProviderId as Invoici16_37_0_, visititems0_.SourceVisitItemId as SourceV17_37_0_ FROM BAR.VisitItems visititems0_ WHERE visititems0_.VisitId=@p0',N'@p0 int',@p0=3826
go
exec sp_executesql N'INSERT INTO BAR.VisitItems (VisitType, FeeItemId, FeeRateType, Amount, GST, Quantity, Total, ServiceFeeType, ServiceText, InvoiceToCentre, IsDefault, OverrideCode, IsSurchargeItem, VisitId, InvoicingProviderId, SourceVisitItemId) VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8, @p9, @p10, @p11, @p12, @p13, @p14, @p15); select SCOPE_IDENTITY()',N'@p0 int,@p1 int,@p2 int,@p3 decimal(28,5),@p4 decimal(28,5),@p5 int,@p6 decimal(28,5),@p7 int,@p8 nvarchar(4000),@p9 bit,@p10 bit,@p11 int,@p12 bit,@p13 int,@p14 int,@p15 int',@p0=1,@p1=452,@p2=1,@p3=0,@p4=0,@p5=1,@p6=0,@p7=1,@p8=NULL,@p9=0,@p10=1,@p11=0,@p12=0,@p13=3826,@p14=3535,@p15=NULL
go
exec sp_executesql N'UPDATE BAR.Visits SET VisitType = @p0, DateOfService = @p1, InvoiceAnnotation = @p2, DefaultItemOverride = @p3, AppointmentId = @p4, ReferralRequired = @p5, ReferralCarePlan = @p6, UserId = @p7, PatientId = @p8, WorkAreaId = @p9, DidNotWaitAdjustmentId = @p10, ReferralId = @p11 WHERE Id = @p12',N'@p0 int,@p1 datetimeoffset(7),@p2 nvarchar(4000),@p3 bit,@p4 int,@p5 bit,@p6 nvarchar(4000),@p7 int,@p8 int,@p9 int,@p10 int,@p11 int,@p12 int',@p0=1,@p1='2016-01-22 12:37:06.8915296 +08:00',@p2=NULL,@p3=0,@p4=NULL,@p5=0,@p6=NULL,@p7=3535,@p8=4246,@p9=2741,@p10=NULL,@p11=NULL,@p12=3826
go
exec sp_executesql N'DELETE FROM BAR.VisitItems WHERE Id = @p0',N'@p0 int',@p0=7919
go

Jetzt scheint meine Sperre einen Effekt zu haben, da sie im Deadlock-Diagramm angezeigt wird. Aber was? Drei exklusive Schlösser und ein gemeinsames Schloss? Wie funktioniert das mit demselben Objekt / Schlüssel? Ich dachte, solange Sie eine exklusive Sperre haben, können Sie keine gemeinsame Sperre von jemand anderem erhalten? Und umgekehrt. Wenn Sie eine gemeinsame Sperre haben, kann niemand eine exklusive Sperre erhalten, er muss warten.

Ich glaube, hier fehlt mir ein tieferes Verständnis dafür, wie die Schlösser funktionieren, wenn sie mit mehreren Schlüsseln am selben Tisch belegt sind.

Hier sind einige der Dinge, die ich ausprobiert habe und deren Auswirkungen:

  • Der lock-Anweisung wurde ein weiterer Indexhinweis für IX_Visit_Id hinzugefügt. Keine Änderung
  • Eine zweite Spalte zur IX_Visit_Id hinzugefügt (die ID der VisitItem-Spalte). weit hergeholt, aber trotzdem versucht. Keine Änderung
  • Die Isolationsstufe wurde wieder in "Festgeschrieben" geändert (Standardeinstellung in unserem Projekt), Deadlocks treten weiterhin auf
  • Die Isolationsstufe wurde in serialisierbar geändert. Deadlocks passieren immer noch, aber schlimmer (verschiedene Grafiken). Das will ich sowieso nicht wirklich.
  • Wenn sie ein Tischschloss nehmen, verschwinden sie (offensichtlich), aber wer würde das wollen?
  • Es funktioniert, eine pessimistische Anwendungssperre (mit sp_getapplock) zu verwenden, aber das ist so ziemlich das Gleiche wie die Tabellensperre. Ich möchte das nicht tun.
  • Das Hinzufügen des READPAST-Hinweises zum XLOCK-Hinweis machte keinen Unterschied
  • Ich habe PageLock für den Index und PK deaktiviert, kein Unterschied
  • Ich habe dem XLOCK-Hinweis einen ROWLOCK-Hinweis hinzugefügt, der keinen Unterschied macht

Einige Randnotizen zu NHibernate: Die Art und Weise, wie es verwendet wird und wie es funktioniert, ist, dass es die SQL-Anweisungen zwischenspeichert, bis es wirklich notwendig ist, sie auszuführen, es sei denn, Sie rufen Flush auf, was wir versuchen, nicht zu tun. So werden die meisten Anweisungen (zB die träge geladene Aggregatliste von VisitItems => Visit.VisitItems) nur bei Bedarf ausgeführt. Die meisten Aktualisierungs- und Löschanweisungen aus meiner Transaktion werden am Ende der Transaktion ausgeführt (wie aus dem obigen SQL-Trace hervorgeht). Ich habe wirklich keine Kontrolle über die Ausführungsreihenfolge. NHibernate entscheidet, wann was zu tun ist. Meine anfängliche Sperranweisung ist eigentlich nur ein Workaround.

Außerdem lese ich mit der lock-Anweisung die Elemente nur in eine nicht verwendete Liste ein (ich versuche nicht, die VisitItems-Liste im Visit-Objekt zu überschreiben, da NHibernate so weit wie möglich nicht funktionieren soll). Obwohl ich die Liste zuerst mit der benutzerdefinierten Anweisung gelesen habe, lädt NHibernate die Liste dennoch erneut in die Proxyobjektsammlung Visit.VisitItems. Dabei wird ein separater SQL-Aufruf verwendet, den ich im Trace sehen kann, wenn es Zeit ist, sie träge irgendwo zu laden.

Aber das sollte doch egal sein, oder? Ich habe bereits das Schloss für den besagten Schlüssel? Wird dies nicht durch erneutes Laden geändert?

Als letzte Anmerkung, vielleicht zur Verdeutlichung: Jeder Prozess fügt zuerst seinen eigenen Besuch mit VisitItems hinzu, geht dann hinein und ändert ihn (was das Löschen und Einfügen und den Deadlock auslöst). In meinen Tests gibt es nie einen Prozess, der genau denselben Visit oder dieselben VisitItems ändert.

Hat jemand eine Idee, wie er das weiter angehen kann? Kann ich irgendetwas versuchen, um dies auf intelligente Weise zu umgehen (keine Tischsperren usw.)? Außerdem möchte ich erfahren, warum diese Tripple-X-Sperre sogar für dasselbe Objekt möglich ist. Ich verstehe nicht

Bitte lassen Sie mich wissen, wenn Sie weitere Informationen benötigen, um das Rätsel zu lösen.

[EDIT] Ich habe die Frage mit der DDL für die beiden betroffenen Tabellen aktualisiert.

Außerdem wurde ich um Klärung der Erwartung gebeten: Ja, ein paar Deadlocks hier und da sind in Ordnung, wir werden es einfach wiederholen oder den Benutzer erneut einreichen lassen (im Allgemeinen). Aber bei der gegenwärtigen Frequenz mit 12 gleichzeitigen Benutzern würde ich erwarten, dass es höchstens alle paar Stunden eine gibt. Derzeit werden sie mehrmals pro Minute angezeigt.

Darüber hinaus habe ich weitere Informationen zu trancount = 2 erhalten, die möglicherweise auf ein Problem mit verschachtelten Transaktionen hinweisen, die wir nicht wirklich verwenden. Ich werde das auch untersuchen und die Ergebnisse hier dokumentieren.


2
Verwenden Sie nicht SELECT *. Es könnte ein Faktor sein, der zu Ihren Problemen beiträgt. Siehe stackoverflow.com/questions/3639861/…
JamieSee

Führen Sie außerdem SELECT OBJECT_NAME(objectid, dbid) AS objectname, * FROM sys.dm_exec_sql_text(0x0200000024a9e43033ef90bb631938f939038627209baafb0000000000000000000000000000000000000000)für jeden executionStack-Frame den Befehl sqlhandle aus, um zu bestimmen, was tatsächlich ausgeführt wird.
JamieSee

Stoßen Sie vielleicht auf eine Hash-Kollision? dba.stackexchange.com/questions/80088/insert-only-deadlocks/…
Johnboy

Hey Leute, ich fürchte, ich bin nicht mehr Teil dieses Projekts: - /, also kann ich deine Vorschläge nicht ausprobieren. Ich habe den Thread und alle Informationen jedoch an einige Teammitglieder weitergeleitet, damit sie ihn an meiner Stelle untersuchen können.
Ben

Sie können meine PowerShell-Skriptantwort auf diese Frage verwenden, um weitere Deadlock-Details abzurufen, die Ihnen möglicherweise helfen. Insbesondere werden die SQL-Anweisungsinformationen für Ihre "unbekannten" Stack-Frames abgerufen. dba.stackexchange.com/questions/28996/…
JamieSee

Antworten:


2

Ich habe ein paar Kommentare dazu abgegeben, bin mir aber nicht sicher, ob Sie die gewünschten Ergebnisse erzielen, wenn Sie die Isolationsstufe für wiederholbare Lesetransaktionen mit Read Committed Snapshot kombinieren.

Die in Ihrer Deadlock-Liste angegebene TIL ist wiederholbar. Dies ist noch restriktiver als Read Committed und führt bei dem von Ihnen beschriebenen Ablauf wahrscheinlich zu Deadlocks.

Möglicherweise möchten Sie, dass Ihre DB-TIL-Datei reproduzierbar gelesen wird, legen jedoch fest, dass die Transaktion die Momentaufnahme-TIL-Datei explizit mit einer festgelegten Anweisung für die Transaktionsisolationsstufe verwendet. Referenz: https://msdn.microsoft.com/en-us/library/ms173763.aspx Wenn ja, denke ich, müssen Sie etwas falsch haben. Ich bin nicht mit nHibernate vertraut, aber es sieht so aus, als gäbe es hier eine Referenz: http://www.anujvarma.com/fluent-nhibernate-setting-database-transaction-isolation-level/

Wenn die Architektur Ihrer App dies zulässt, besteht eine Option darin, einen festgeschriebenen Snapshot auf Datenbankebene zu lesen. Wenn weiterhin Deadlocks auftreten, aktivieren Sie den Snapshot mit Zeilenversionierung. Beachten Sie, dass Sie in diesem Fall Ihr Tempdb-Setup überdenken müssen, wenn Sie Snapshot (Zeilenversionierung) aktivieren. Ich kann Ihnen alles Mögliche Material besorgen, wenn Sie es brauchen - lassen Sie es mich wissen.


2

Ich habe ein paar Gedanken. Der einfachste Weg, Deadlocks zu vermeiden, besteht darin, die Sperren immer in der gleichen Reihenfolge auszuführen. Das bedeutet, dass ein anderer Code, der explizite Transaktionen verwendet, auf Objekte in derselben Reihenfolge zugreifen sollte, aber auch der Zugriff auf Zeilen, die einzeln nach dem Schlüssel einer expliziten Transaktion erfolgen, nach diesem Schlüssel sortiert werden sollte. Versuchen Sie das Sortieren Visit.VisitItemsvon seiner PK vor tun Addoder Deletees sei denn , dies eine riesige Sammlung ist in diesem Fall würde ich sortieren SELECT.

Das Sortieren ist hier jedoch wahrscheinlich nicht Ihr Problem. Ich vermute, dass 2 Threads gemeinsam genutzte Sperren für alle VisitItemIDs für eine gegebene VisitIDund Thread A DELETEnicht abgeschlossen werden können, bis Thread B seine gemeinsam genutzte Sperre freigibt, die es nicht wird, bis es DELETEabgeschlossen ist. App-Sperren funktionieren hier und sind nicht so schlecht wie Tabellensperren, da sie nur nach Methode blockiert werden und andere SELECTs gut funktionieren. Sie könnten auch eine exklusive Sperre für den VisitTisch für die gegebene nehmen, VisitIDaber wieder, das ist möglicherweise übertrieben.

Ich würde empfehlen, aus Ihrem harten Löschvorgang einen weichen Löschvorgang zu machen ( UPDATE ... SET IsDeleted = 1anstatt ihn zu verwenden DELETE) und diese Datensätze später in großen Mengen mit einem Bereinigungsjob zu bereinigen, der keine expliziten Transaktionen verwendet. Dies erfordert natürlich das Umgestalten von anderem Code, um diese gelöschten Zeilen zu ignorieren. Dies ist jedoch meine bevorzugte Methode zum Behandeln von DELETEs, die SELECTin einer expliziten Transaktion enthalten sind.

Sie können auch die SELECTaus der Transaktion entfernen und zu einem optimistischen Parallelitätsmodell wechseln. Entity Framework macht dies kostenlos, nicht sicher über NHibernate. EF würde eine optimistische Nebenläufigkeitsausnahme auslösen, wenn Ihre DELETERückgaben 0 betroffene Zeilen sind.


1

Haben Sie versucht, das Visits-Update zu verschieben, bevor Sie Änderungen an visitItems vorgenommen haben? Diese X-Sperre sollte die "untergeordneten" Zeilen schützen.

Das Durchführen eines vollständigen Trace für die erfassten Sperren (und die Konvertierung in eine für Menschen lesbare Version) ist sehr aufwändig, kann jedoch die Reihenfolge deutlicher anzeigen.



-1

READ COMMITTED SNAPSHOT ON bedeutet, dass jede einzelne Transaktion, die in READ COMMITTED ISOLATION LEVEL ausgeführt wird, als READ COMMITTED SNAPSHOT fungiert.

Dies bedeutet, dass Leser keine Schreiber und Schreiber keine Leser blockieren.

Sie verwenden die Isolationsstufe für wiederholbare Lesetransaktionen. Aus diesem Grund ist ein Deadlock aufgetreten. Read Committed (ohne Snapshot) hält die Sperren für die Zeilen / Seiten bis zum Ende der Anweisung , während Repeatable Read die Sperren bis zum Ende der Transaktion hält .

Wenn Sie sich Ihr Deadlock-Diagramm ansehen, sehen Sie, dass eine "S" -Sperre erworben wurde. Ich denke, dies ist die Sperre durch den zweiten Punkt -> "Lesen Sie alle Besuchselemente für einen bestimmten Besuch von VisitId."

  1. Ändern Sie die Transaktionsisolationsstufe Ihrer NHibernate-Verbindungen in Read Committed
  2. Sie müssen die Abfrage auf Ihren zweiten Punkt analysieren und verstehen, warum sie Sperren für die PK erhält, wenn Sie einen Index für Ihre visitID-Spalte haben (möglicherweise aufgrund fehlender eingeschlossener Spalten in Ihrem Index).
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.