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_activity
Tabelle 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_activity
die 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-S
Schlüsselsperre für denIX_follow_member_id_includes
Index, während SP2 eine in Konflikt stehende (X) Sperre enthältSP2 wartet auf eine
S
Modussperre,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_includes
Index 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_id
ist ein Fremdschlüssel für einemember
Tabellemember_activity.activity_id
ist ein Fremdschlüssel für denactivity
Tischfollow.member_id
ist ein Fremdschlüssel für einemember
Tabellefollow.follower_id
ist ein Fremdschlüssel für einemember
Tabelle
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.
quelle
SERIALIZABLE
(es gibt ein bisschen mehr als das, aber dies ist ein Kommentar, keine Antwort :)Antworten:
Der Konflikt läuft darauf hinaus,
network_activity
dass 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 denIX_follow-member_id_includes
Index 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:
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?
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
@Resource
in beiden gespeicherten Prozessen "network_activity" definieren, wodurch sie gezwungen werden, auf ihren Zug zu warten. Jeder Proc würde der gleichen Struktur folgen:Sie müssen die Fehler
ROLLBACK
selbst verwalten (wie in der verknüpften MSDN-Dokumentation angegeben), also das Übliche eingebenTRY...CATCH
. Dies ermöglicht es Ihnen jedoch, die Situation zu managen.Bitte beachten Sie:
sp_getapplock
/sp_releaseapplock
sollte 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.quelle
member_id
in den@Resource
Wert ä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.