Beheben von Deadlocks aus 2 Tabellen, die nur über die indizierte Ansicht verknüpft sind

16

Ich habe eine Situation, in der ich festgefahren bin, und ich glaube, ich habe die Schuldigen eingegrenzt, aber ich bin nicht ganz sicher, was ich tun kann, um das zu beheben.

Dies ist eine Produktionsumgebung, in der SQL Server 2008 R2 ausgeführt wird.

Um Ihnen einen etwas vereinfachten Überblick über die Situation zu geben:


Ich habe 3 Tabellen wie unten definiert:

TABLE activity (
    id, -- PK
    ...
)

TABLE member_activity (
    member_id, -- PK col 1
    activity_id, -- PK col 2
    ...
)

TABLE follow (
    id, -- PK
    follower_id,
    member_id,
    ...
)

In der member_activityTabelle ist ein zusammengesetzter Primärschlüssel definiert als member_id, activity_id, da ich auf diese Weise nur Daten in dieser Tabelle nachschlagen muss.

Ich habe auch einen nicht gruppierten Index für follow:

CREATE NONCLUSTERED INDEX [IX_follow_member_id_includes] 
ON follow ( member_id ASC ) INCLUDE ( follower_id )

Außerdem habe ich eine schemagebundene Ansicht, network_activitydie wie folgt definiert ist:

CREATE VIEW network_activity
WITH SCHEMABINDING
AS

SELECT
    follow.follower_id as member_id,
    member_activity.activity_id as activity_id,
    COUNT_BIG(*) AS cb
FROM member_activity
INNER JOIN follow ON follow.member_id = member_activity.member_id
INNER JOIN activity ON activity.id = member_activity.activity_id
GROUP BY follow.follower_id, member_activity.activity_id

Welches hat auch einen eindeutigen Clustered-Index:

CREATE UNIQUE CLUSTERED INDEX [IX_network_activity_unique_member_id_activity_id] 
ON network_activity
(
    member_id ASC,
    activity_id ASC
)

Jetzt habe ich zwei festgefahrene gespeicherte Prozeduren. Sie durchlaufen den folgenden Prozess:

-- SP1: insert activity
-----------------------
INSERT INTO activity (...)
SELECT ... FROM member_activity WHERE member_id = @a AND activity_id = @b
INSERT INTO member_activity (...)


-- SP2: insert follow
---------------------
SELECT follow WHERE member_id = @x AND follower_id = @y
INSERT INTO follow (...)

Diese beiden Prozeduren werden in READ COMMITTED-Isolation ausgeführt. Es ist mir gelungen, die 1222-Ausgabe für erweiterte Ereignisse abzufragen, und ich habe Folgendes in Bezug auf Deadlocks interpretiert:

SP1 wartet auf eine RangeS-SSchlüsselsperre für den IX_follow_member_id_includesIndex, während SP2 eine in Konflikt stehende (X) Sperre enthält

SP2 wartet auf eine SModussperre, PK_member_activity während SP1 eine in Konflikt stehende (X) Sperre hält

Der Deadlock scheint in der letzten Zeile jeder Abfrage (den Einfügungen) aufzutreten. Was mir unklar ist, warum SP1 eine Sperre für den IX_follow-member_id_includesIndex wünscht . Der einzige Link für mich scheint von dieser indizierten Ansicht zu sein, weshalb ich ihn aufgenommen habe.

Was wäre der beste Weg für mich, um diese Deadlocks zu verhindern? Jede Hilfe wäre sehr dankbar. Ich habe nicht viel Erfahrung in der Lösung von Deadlock-Problemen.

Bitte lassen Sie mich wissen, wenn ich weitere Informationen zur Verfügung stellen kann, die helfen könnten!

Danke im Voraus.


Bearbeiten 1: Hinzufügen weiterer Informationen pro Anfrage.

Hier ist die 1222-Ausgabe dieses Deadlocks:

<deadlock>
    <victim-list>
        <victimProcess id="process4c6672748" />
    </victim-list>
    <process-list>
        <process id="process4c6672748" taskpriority="0" logused="332" waitresource="KEY: 8:72057594104905728 (25014f77eaba)" waittime="581" ownerId="474698706" transactionname="INSERT" lasttranstarted="2014-07-03T17:03:12.287" XDES="0x298487970" lockMode="RangeS-S" schedulerid="1" kpid="972" status="suspended" spid="79" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2014-07-03T17:03:12.283" lastbatchcompleted="2014-07-03T17:03:12.283" lastattention="2014-07-03T10:25:00.283" clientapp=".Net SqlClient Data Provider" hostname="WIN08CLYDESDALE" hostpid="4596" loginname="TechPro" isolationlevel="read committed (2)" xactid="474698706" currentdb="8" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
            <executionStack>
                <frame procname="" line="7" stmtstart="1194" stmtend="1434" sqlhandle="0x02000000a26bb72a2b220406876cad09c22242e5265c82e6" />
                <frame procname="" line="1" sqlhandle="0x000000000000000000000000000000000000000000000000" />
            </executionStack>
            <inputbuf> <!-- SP 1 --> </inputbuf>
        </process>
        <process id="process6cddc5b88" taskpriority="0" logused="456" waitresource="KEY: 8:72057594098679808 (89013169fc76)" waittime="567" ownerId="474698698" transactionname="INSERT" lasttranstarted="2014-07-03T17:03:12.283" XDES="0x30c459970" lockMode="S" schedulerid="4" kpid="4204" status="suspended" spid="70" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2014-07-03T17:03:12.283" lastbatchcompleted="2014-07-03T17:03:12.283" lastattention="2014-07-03T15:04:55.870" clientapp=".Net SqlClient Data Provider" hostname="WIN08CLYDESDALE" hostpid="4596" loginname="TechPro" isolationlevel="read committed (2)" xactid="474698698" currentdb="8" lockTimeout="4294967295" clientoption1="673185824" clientoption2="128056">
            <executionStack>
                <frame procname="" line="18" stmtstart="942" stmtend="1250" sqlhandle="0x03000800ca458d315ee9130100a300000100000000000000" />
            </executionStack>
            <inputbuf> <!-- SP 2 --> </inputbuf>
        </process>
    </process-list>
    <resource-list>
        <keylock hobtid="72057594104905728" dbid="8" objectname="" indexname="" id="lock33299fc00" mode="X" associatedObjectId="72057594104905728">
            <owner-list>
                <owner id="process6cddc5b88" mode="X" />
            </owner-list>
            <waiter-list>
                <waiter id="process4c6672748" mode="RangeS-S" requestType="wait" />
            </waiter-list>
        </keylock>
        <keylock hobtid="72057594098679808" dbid="8" objectname="" indexname="" id="lockb7e2ba80" mode="X" associatedObjectId="72057594098679808">
            <owner-list>
                <owner id="process4c6672748" mode="X" />
            </owner-list>
            <waiter-list>
                <waiter id="process6cddc5b88" mode="S" requestType="wait" />
            </waiter-list>
        </keylock>
    </resource-list>
</deadlock>

In diesem Fall,

AssociatedObjectId 72057594098679808 entspricht member_activity, PK_member_activity

AssociatedObjectId 72057594104905728 entspricht follow, IX_follow_member_id_includes

Hier ist auch ein genaueres Bild darüber, was SP1 und SP2 tun

-- SP1: insert activity
-----------------------
DECLARE @activityId INT

INSERT INTO activity (field1, field2)
VALUES (@field1, @field2)

SET @activityId = SCOPE_IDENTITY();

IF NOT EXISTS(
    SELECT TOP 1 member_id 
    FROM member_activity 
    WHERE member_id = @m1 AND activity_id = @activityId
)
    INSERT INTO member_activity (member_id, activity_id, field1)
    VALUES (@m1, @activityId, @field1)

IF NOT EXISTS(
    SELECT TOP 1 member_id 
    FROM member_activity 
    WHERE member_id = @m2 AND activity_id = @activityId
)
    INSERT INTO member_activity (member_id, activity_id, field1)
    VALUES (@m2, @activityId, @field1)

auch SP2:

-- SP2: insert follow
---------------------

IF NOT EXISTS(
    SELECT TOP 1 1 
    FROM follow
    WHERE member_id = @memberId AND follower_id = @followerId
)
    INSERT INTO follow (member_id, follower_id)
    VALUES (@memberId, @followerId)

Edit 2: Nachdem ich die Kommentare noch einmal gelesen hatte, dachte ich, ich würde ein paar Informationen hinzufügen, welche Spalten auch Fremdschlüssel sind ...

  • member_activity.member_idist ein Fremdschlüssel für eine memberTabelle
  • member_activity.activity_idist ein Fremdschlüssel für den activityTisch
  • follow.member_idist ein Fremdschlüssel für eine memberTabelle
  • follow.follower_idist ein Fremdschlüssel für eine memberTabelle

Update 1:

Ich nahm ein paar Änderungen vor, von denen ich dachte, dass sie helfen könnten, den Deadlock zu verhindern, ohne dass ich Glück hatte.

Ich habe folgende Änderungen vorgenommen:

-- SP1: insert activity
-----------------------
DECLARE @activityId INT

INSERT INTO activity (field1, field2)
VALUES (@field1, @field2)

SET @activityId = SCOPE_IDENTITY();

MERGE member_activity WITH ( HOLDLOCK ) as target
USING (SELECT @m1 as member_id, @activityId as activity_id, @field1 as field1) as source
    ON target.member_id = source.member_id
    AND target.activity_id = source.activity_id
WHEN NOT MATCHED THEN
    INSERT (member_id, activity_id, field1)
    VALUES (source.member_id, source.activity_id, source.field1)
;

MERGE member_activity WITH ( HOLDLOCK ) as target
USING (SELECT @m2 as member_id, @activityId as activity_id, @field1 as field1) as source
    ON target.member_id = source.member_id
    AND target.activity_id = source.activity_id
WHEN NOT MATCHED THEN
    INSERT (member_id, activity_id, field1)
    VALUES (source.member_id, source.activity_id, source.field1)
;

und mit SP2:

-- SP2: insert follow
---------------------

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION

IF NOT EXISTS(
    SELECT TOP 1 1 
    FROM follow WITH ( UPDLOCK )
    WHERE member_id = @memberId AND follower_id = @followerId
)
    INSERT INTO follow (member_id, follower_id)
    VALUES (@memberId, @followerId)

COMMIT

Mit diesen beiden Änderungen bekomme ich immer noch Deadlocks.

Wenn ich noch etwas anbieten kann, lass es mich bitte wissen. Vielen Dank.

Leland Richardson
quelle
read commit akzeptiert keine Tastensperren, nur serialisierbare. Wenn der Deadlock tatsächlich "read commit" (2) anzeigt, haben Sie wahrscheinlich Zugriff auf das Ändern eines Fremdschlüssels, der im Hintergrund serialisierbar wird (obwohl immer noch "read commit" angezeigt wird). Wir würden ehrlich gesagt das gesamte ddl und die sps brauchen, um weiterzuhelfen.
Sean sagt Entfernen Sara Chipps
@ SeanGallardy, danke. Ich habe die 1222-Ausgabe für den Fall bearbeitet, dass ich falsch interpretiert habe, und ich habe weitere Details zu den SPs hinzugefügt. Hilft das?
Leland Richardson
2
@ SeanGallardy Der Teil des Abfrageplans, der die indizierte Ansicht verwaltet, wird intern unter ausgeführt SERIALIZABLE(es gibt ein bisschen mehr als das, aber dies ist ein Kommentar, keine Antwort :)
Paul White Reinstate Monica
@PaulWhite Danke für den Einblick, das wusste ich nicht! Bei einem kurzen Test kann ich die serialisierbaren Sperrmodi mit der indizierten Ansicht während des Einfügens in Ihre gespeicherten Prozeduren (RangeI-N, RangeS-S, RangeS-U) definitiv abrufen. Es scheint, als ob der Deadlock durch die inkompatiblen Sperrmodi verursacht wird, die zum richtigen Zeitpunkt während der Einfügungen in Ihre gespeicherten Prozeduren aufeinander treffen, wenn sie innerhalb der Sperrgrenzen liegen (z. B. in dem Bereich, der von der Bereichssperre gehalten wird). Sowohl eine Timing- als auch eine Eingabedatenkollision würde ich denken.
Sean sagt Entfernen Sara Chipps
Frage: Wenn ich einen HOLDLOCK-Hinweis zu den SELECT-Anweisungen hinzufügen würde, würde dies verhindern, dass die Sperre beim Einfügen auftritt?
Leland Richardson

Antworten:

4

Der Konflikt läuft darauf hinaus, network_activitydass es sich um eine indizierte Ansicht handelt, die (intern) in den DML-Anweisungen verwaltet werden muss. Dies ist der wahrscheinlichste Grund, warum SP1 eine Sperre für den IX_follow-member_id_includesIndex wünscht, da dieser wahrscheinlich von der Ansicht verwendet wird (es scheint sich um einen übergeordneten Index für die Ansicht zu handeln).

Zwei mögliche Optionen:

  1. Ziehen Sie in Betracht, den Clustered Index in der Ansicht abzulegen, damit es sich nicht mehr um eine indizierte Ansicht handelt. Wiegt der Nutzen der Wartung die Wartungskosten auf? Wählen Sie häufig genug aus, oder lohnt sich der Leistungsgewinn durch die Indexierung? Wenn Sie diese Prozesse häufig ausführen, sind die Kosten möglicherweise höher als der Nutzen?

  2. Wenn der Vorteil der Indizierung der Ansicht die Kosten überwiegt, sollten Sie die DML-Operationen gegen die Basistabellen dieser Ansicht isolieren. Dies kann mithilfe von Anwendungssperren erfolgen (siehe sp_getapplock und sp_releaseapplock ). Mit Anwendungssperren können Sie Sperren für beliebige Konzepte erstellen. Das heißt, Sie können @Resourcein beiden gespeicherten Prozessen "network_activity" definieren, wodurch sie gezwungen werden, auf ihren Zug zu warten. Jeder Proc würde der gleichen Struktur folgen:

    BEGIN TRANSACTION;
    EXEC sp_getapplock @Resource = 'network_activity', @LockMode = 'Exclusive';
    ...current proc code...
    EXEC sp_releaseapplock @Resource = 'network_activity';
    COMMIT TRANSACTION;

    Sie müssen die Fehler ROLLBACKselbst verwalten (wie in der verknüpften MSDN-Dokumentation angegeben), also das Übliche eingeben TRY...CATCH. Dies ermöglicht es Ihnen jedoch, die Situation zu managen.
    Bitte beachten Sie: sp_getapplock / sp_releaseapplocksollte sparsam verwendet werden; Application Locks können auf jeden Fall sehr praktisch sein (wie in solchen Fällen), sollten jedoch nur verwendet werden, wenn dies unbedingt erforderlich ist.

Solomon Rutzky
quelle
Danke für die Hilfe. Ich werde ein bisschen mehr über Option 2 nachlesen und sehen, ob das bei uns funktioniert. Die Ansicht wird von ziemlich viel gelesen, und der Clustered-Index ist eine ziemlich große Hilfe ... also möchte ich ihn noch nicht entfernen. Ich werde ein Update zurückkommen, sobald ich es ausprobiert habe.
Leland Richardson
Ich denke mit sp_getapplock wird funktionieren. Ich konnte es in unserer Produktionsumgebung noch nicht ausprobieren, aber ich wollte sicherstellen, dass Sie die Prämie erhalten haben, bevor sie abgelaufen ist. Ich werde hier aktualisieren, wenn ich bestätigen kann, dass es funktioniert!
Leland Richardson
Vielen Dank. Eine nette Sache bei Application Locks ist, dass Sie die Granularität der Verkettung in so etwas wie dem member_idin den @ResourceWert ändern können . Das scheint nicht auf diese spezielle Situation zuzutreffen, aber ich habe gesehen, dass es so verwendet wird, und es ist sehr praktisch, insbesondere in einem System mit mehreren Mandanten, in dem Sie den Prozess auf einen einzelnen Thread pro Kunde beschränken möchten, aber Immer noch muss es kundenübergreifend multithreaded werden.
Solomon Rutzky
Ich wollte ein Update geben und sagen , dass dies hat in der Produktionsumgebung am Ende arbeiten. :)
Leland Richardson