In einer unserer Datenbanken haben wir eine Tabelle, auf die mehrere Threads gleichzeitig intensiv zugreifen. Threads aktualisieren oder fügen Zeilen über ein MERGE
. Es gibt auch Threads, die gelegentlich Zeilen löschen, sodass Tabellendaten sehr flüchtig sind. Threads, die Upsts machen, leiden manchmal unter Deadlocking. Das Problem ähnelt dem in dieser Frage beschriebenen. Der Unterschied besteht jedoch darin, dass in unserem Fall jeder Thread genau eine Zeile aktualisiert oder einfügt .
Es folgt eine vereinfachte Einrichtung. Die Tabelle ist ein Heap mit zwei eindeutigen nicht gruppierten Indizes
CREATE TABLE [Cache]
(
[UID] uniqueidentifier NOT NULL CONSTRAINT DF_Cache_UID DEFAULT (newid()),
[ItemKey] varchar(200) NOT NULL,
[FileName] nvarchar(255) NOT NULL,
[Expires] datetime2(2) NOT NULL,
CONSTRAINT [PK_Cache] PRIMARY KEY NONCLUSTERED ([UID])
)
GO
CREATE UNIQUE INDEX IX_Cache ON [Cache] ([ItemKey]);
GO
und die typische Abfrage ist
DECLARE
@itemKey varchar(200) = 'Item_0F3C43A6A6A14255B2EA977EA730EDF2',
@fileName nvarchar(255) = 'File_0F3C43A6A6A14255B2EA977EA730EDF2.dat';
MERGE INTO [Cache] WITH (HOLDLOCK) T
USING (
VALUES (@itemKey, @fileName, dateadd(minute, 10, sysdatetime()))
) S(ItemKey, FileName, Expires)
ON T.ItemKey = S.ItemKey
WHEN MATCHED THEN
UPDATE
SET
T.FileName = S.FileName,
T.Expires = S.Expires
WHEN NOT MATCHED THEN
INSERT (ItemKey, FileName, Expires)
VALUES (S.ItemKey, S.FileName, S.Expires)
OUTPUT deleted.FileName;
Das heißt, der Abgleich erfolgt über einen eindeutigen Indexschlüssel. Hinweis HOLDLOCK
ist hier wegen Parallelität (wie hier empfohlen ).
Ich habe kleine Nachforschungen angestellt und Folgendes gefunden.
In den meisten Fällen ist der Abfrageausführungsplan
mit dem folgenden Verriegelungsmuster
dh IX
Sperren des Objekts, gefolgt von detaillierteren Sperren.
Manchmal ist der Ausführungsplan für Abfragen jedoch anders
(Diese Planform kann durch Hinzufügen eines INDEX(0)
Hinweises erzwungen werden ) und das Sperrmuster ist
Hinweis X
Sperre auf Objekt IX
platziert, nachdem bereits platziert wurde.
Da zwei IX
kompatibel sind, zwei X
jedoch nicht, geschieht dies unter Parallelität
Deadlock !
Und hier stellt sich der erste Teil der Frage . Ist das X
Sperren eines Objekts nach der IX
Berechtigung zulässig? Ist es nicht ein Fehler?
Absichtssperren werden als Absichtssperren bezeichnet, da sie vor einer Sperre auf der unteren Ebene erfasst werden und daher die Absicht signalisieren, Sperren auf einer niedrigeren Ebene zu platzieren .
und auch
IX bedeutet die Absicht, nur einige der Zeilen und nicht alle zu aktualisieren
so, platzieren X
Sperre auf Objekt nach IX
Aussehen mir sehr verdächtig.
Zuerst habe ich versucht, Deadlocking zu verhindern, indem ich versucht habe, Hinweise zum Sperren von Tabellen hinzuzufügen
MERGE INTO [Cache] WITH (HOLDLOCK, TABLOCK) T
und
MERGE INTO [Cache] WITH (HOLDLOCK, TABLOCKX) T
mit dem TABLOCK
an Ort und Stelle wird das Verriegelungsmuster
und mit dem TABLOCKX
Verriegelungsmuster ist
Da zwei SIX
(sowie zwei X
) nicht kompatibel sind, verhindert dies effektiv Deadlocks, aber leider auch Parallelität (was nicht erwünscht ist).
Meine nächsten Versuche waren das Hinzufügen PAGLOCK
und ROWLOCK
Verbessern von Sperren und das Reduzieren von Konflikten. Beides hat keine Auswirkung ( X
auf das Objekt wurde noch unmittelbar danach beobachtet IX
).
Mein letzter Versuch bestand darin, eine "gute" Ausführungsplanform mit einer guten granularen Verriegelung durch Hinzufügen eines FORCESEEK
Hinweises zu erzwingen
MERGE INTO [Cache] WITH (HOLDLOCK, FORCESEEK(IX_Cache(ItemKey))) T
und es hat funktioniert.
Und hier stellt sich der zweite Teil der Frage . Könnte es passieren, dass FORCESEEK
dies ignoriert wird und ein schlechtes Sperrmuster verwendet wird? (Wie ich bereits erwähnte PAGLOCK
und ROWLOCK
scheinbar ignoriert wurde).
Das Hinzufügen UPDLOCK
hat keine Auswirkung ( X
auf das Objekt, das danach noch sichtbar ist IX
).
Das IX_Cache
erwartete Clustering des Index hat funktioniert. Dies führte zu einem Plan mit Clustered Index Seek und granularem Sperren. Zusätzlich habe ich versucht, Clustered Index Scan zu erzwingen , bei dem auch granulares Sperren angezeigt wurde.
Jedoch. Zusätzliche Beobachtung. Im ursprünglichen Setup wird der Ausführungsplan auch dann ausgeführt FORCESEEK(IX_Cache(ItemKey)))
, wenn eine @itemKey
Variablendeklaration von varchar (200) in nvarchar (200) geändert wird
Sehen Sie, dass die Suche verwendet wird. ABER das Sperrmuster zeigt in diesem Fall erneut die X
Sperre, die nach dem Objekt platziert wurde IX
.
Es scheint also, dass das Erzwingen der Suche nicht unbedingt granulare Sperren garantiert (und daher das Fehlen von Deadlocks). Ich bin nicht sicher, ob ein Clustered-Index eine granulare Sperrung garantiert. Oder doch?
Mein Verständnis (korrigieren Sie mich, wenn ich falsch liege) ist, dass das Sperren in hohem Maße situativ ist und eine bestimmte Form des Ausführungsplans kein bestimmtes Sperrmuster impliziert.
Die Frage nach der Berechtigung, X
ein Objekt zu sperren, nachdem es IX
noch geöffnet ist. Und wenn es berechtigt ist, gibt es etwas, das man tun kann, um das Sperren von Objekten zu verhindern?