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?
quelle
Antworten:
Es sieht ein bisschen seltsam aus, ist aber gültig. Zum Zeitpunkt der
IX
Aufnahme kann die Absicht durchaus darin bestehen,X
Sperren auf einer niedrigeren Ebene zu nehmen. Es gibt nichts zu sagen, dass solche Schlösser tatsächlich genommen werden müssen. Schließlich gibt es auf der unteren Ebene möglicherweise nichts zu sperren. Der Motor kann das nicht im Voraus wissen. Darüber hinaus kann es Optimierungen geben, bei denen Sperren niedrigerer Ebene übersprungen werden können (ein Beispiel fürIS
undS
Sperren finden Sie hier ).Insbesondere für das aktuelle Szenario ist es wahr, dass serialisierbare Schlüsselbereichssperren für einen Heap nicht verfügbar sind. Daher ist die einzige Alternative eine
X
Sperre auf Objektebene. In diesem Sinne kann die Engine möglicherweise frühzeitig erkennen, dass eineX
Sperre zwangsläufig erforderlich ist, wenn es sich bei der Zugriffsmethode um einen Heap-Scan handelt, und daher dieIX
Sperre vermeiden .Andererseits ist das Sperren komplex, und Absichtssperren können manchmal aus internen Gründen vorgenommen werden, die nicht unbedingt mit der Absicht zusammenhängen, Sperren niedrigerer Ebene zu verwenden. Die Einnahme
IX
kann die am wenigsten invasive Methode sein, um einen erforderlichen Schutz für einen Fall mit dunkler Kante zu bieten. Eine ähnliche Überlegung finden Sie unter Gemeinsame Sperre für IsolationLevel.ReadUncommitted .Die aktuelle Situation ist für Ihr Deadlock-Szenario unglücklich und kann im Prinzip vermieden werden, aber das ist nicht unbedingt dasselbe wie ein "Bug". Sie können das Problem über Ihren normalen Support-Kanal oder über Microsoft Connect melden, wenn Sie eine endgültige Antwort darauf benötigen.
Nein
FORCESEEK
ist weniger ein Hinweis als eine Richtlinie. Wenn der Optimierer keinen Plan findet, der den "Hinweis" berücksichtigt, wird ein Fehler ausgegeben.Durch Erzwingen des Index wird sichergestellt, dass Sperren für Schlüsselbereiche ausgeführt werden können. Zusammen mit den Aktualisierungssperren, die natürlich bei der Verarbeitung einer Zugriffsmethode zum Ändern von Zeilen verwendet werden, bietet dies eine ausreichende Garantie, um Parallelitätsprobleme in Ihrem Szenario zu vermeiden.
Wenn sich das Schema der Tabelle nicht ändert (z. B. Hinzufügen eines neuen Index), reicht der Hinweis auch aus, um zu verhindern, dass diese Abfrage mit sich selbst blockiert. Es besteht weiterhin die Möglichkeit eines zyklischen Deadlocks mit anderen Abfragen, die möglicherweise auf den Heap vor dem nicht gruppierten Index zugreifen (z. B. eine Aktualisierung des Schlüssels des nicht gruppierten Index).
Dies unterbricht die Garantie, dass eine einzelne Reihe betroffen ist, sodass eine Eager-Tischspule zum Schutz vor Halloween eingeführt wird. Machen Sie als weitere Problemumgehung die Garantie mit explizit
MERGE TOP (1) INTO [Cache]...
.In einem Ausführungsplan ist sicherlich noch viel mehr los. Sie können eine bestimmte Planform mit z. B. einer Planführung erzwingen, der Motor entscheidet sich jedoch möglicherweise zur Laufzeit für andere Sperren. Die Chancen sind ziemlich gering, wenn Sie das
TOP (1)
obige Element einbeziehen .Allgemeine Bemerkungen
Es ist etwas ungewöhnlich, dass eine Heap-Tabelle auf diese Weise verwendet wird. Sie sollten die Vorteile der Konvertierung in eine Clustertabelle in Betracht ziehen, möglicherweise unter Verwendung des Index, den Dan Guzman in einem Kommentar vorgeschlagen hat:
Dies kann wichtige Vorteile bei der Wiederverwendung von Speicherplatz haben und eine gute Problemumgehung für das aktuelle Deadlocking-Problem bieten.
MERGE
ist auch in einer Umgebung mit hoher Parallelität etwas ungewöhnlich. Etwas kontraintuitiv ist es oft effizienter, separate AnweisungenINSERT
undUPDATE
Anweisungen auszuführen , zum Beispiel:Beachten Sie, dass die RID-Suche nicht mehr erforderlich ist:
Wenn Sie die Existenz eines eindeutigen Index für garantieren können
ItemKey
(wie in der Frage) die redundantenTOP (1)
in derUPDATE
entfernt werden kann, desto einfachen Plan zu geben:Beide
INSERT
undUPDATE
Pläne qualifizieren sich in beiden Fällen für einen trivialen Plan.MERGE
erfordert immer eine vollständige kostenbasierte Optimierung.Das richtige zu verwendende Muster und weitere Informationen finden Sie im zugehörigen Problem mit der gleichzeitigen Eingabe von SQL Server 2014 für gleichzeitige Eingaben
MERGE
.Deadlocks können nicht immer verhindert werden. Sie können durch sorgfältige Codierung und Gestaltung auf ein Minimum reduziert werden. Die Anwendung sollte jedoch immer darauf vorbereitet sein, den ungeraden Deadlock ordnungsgemäß zu handhaben (z. B. Bedingungen erneut prüfen und dann erneut versuchen).
Wenn Sie die vollständige Kontrolle über die Prozesse haben, die auf das betreffende Objekt zugreifen, können Sie auch Anwendungssperren verwenden, um den Zugriff auf einzelne Elemente zu serialisieren, wie unter SQL Server Concurrent Inserts and Deletes beschrieben .
quelle