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 HOLDLOCKist 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 IXSperren 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 XSperre auf Objekt IXplatziert, nachdem bereits platziert wurde.
Da zwei IXkompatibel sind, zwei Xjedoch nicht, geschieht dies unter Parallelität
Deadlock !
Und hier stellt sich der erste Teil der Frage . Ist das XSperren eines Objekts nach der IXBerechtigung 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 XSperre auf Objekt nach IXAussehen 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 TABLOCKan Ort und Stelle wird das Verriegelungsmuster
und mit dem TABLOCKXVerriegelungsmuster 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 PAGLOCKund ROWLOCKVerbessern von Sperren und das Reduzieren von Konflikten. Beides hat keine Auswirkung ( Xauf 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 FORCESEEKHinweises 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 FORCESEEKdies ignoriert wird und ein schlechtes Sperrmuster verwendet wird? (Wie ich bereits erwähnte PAGLOCKund ROWLOCKscheinbar ignoriert wurde).
Das Hinzufügen UPDLOCKhat keine Auswirkung ( Xauf das Objekt, das danach noch sichtbar ist IX).
Das IX_Cacheerwartete 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 @itemKeyVariablendeklaration 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 XSperre, 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, Xein Objekt zu sperren, nachdem es IXnoch geöffnet ist. Und wenn es berechtigt ist, gibt es etwas, das man tun kann, um das Sperren von Objekten zu verhindern?










