Ich habe das folgende Verfahren (SQL Server 2008 R2):
create procedure usp_SaveCompanyUserData
@companyId bigint,
@userId bigint,
@dataTable tt_CoUserdata readonly
as
begin
set nocount, xact_abort on;
merge CompanyUser with (holdlock) as r
using (
select
@companyId as CompanyId,
@userId as UserId,
MyKey,
MyValue
from @dataTable) as newData
on r.CompanyId = newData.CompanyId
and r.UserId = newData.UserId
and r.MyKey = newData.MyKey
when not matched then
insert (CompanyId, UserId, MyKey, MyValue) values
(@companyId, @userId, newData.MyKey, newData.MyValue);
end;
CompanyId, UserId, MyKey bilden den zusammengesetzten Schlüssel für die Zieltabelle. CompanyId ist ein Fremdschlüssel für eine übergeordnete Tabelle. Es gibt auch einen nicht gruppierten Index für CompanyId asc, UserId asc
.
Es wird von vielen verschiedenen Threads aufgerufen, und ich erhalte ständig Deadlocks zwischen verschiedenen Prozessen, die dieselbe Anweisung aufrufen. Mein Verständnis war, dass das "with (holdlock)" notwendig war, um Fehler beim Einfügen / Aktualisieren von Rennbedingungen zu vermeiden.
Ich gehe davon aus, dass zwei verschiedene Threads Zeilen (oder Seiten) in unterschiedlicher Reihenfolge sperren, wenn sie die Einschränkungen validieren, und somit Deadlocks verursachen.
Ist das eine richtige Annahme?
Was ist der beste Weg, um diese Situation zu lösen (dh keine Deadlocks, minimale Auswirkung auf die Multi-Thread-Leistung)?
(Wenn Sie das Bild in einem neuen Tab anzeigen, ist es lesbar. Entschuldigen Sie die geringe Größe.)
- Es sind höchstens 28 Zeilen in der @datatable.
- Ich habe den Code zurückverfolgt und sehe nirgendwo, dass wir hier eine Transaktion starten.
- Der Fremdschlüssel ist so eingerichtet, dass er nur beim Löschen kaskadiert, und es wurden keine Löschvorgänge aus der übergeordneten Tabelle ausgeführt.
Es würde kein Problem geben, wenn die Tabellenvariable immer nur einen Wert enthalten würde. Bei mehreren Zeilen gibt es eine neue Möglichkeit für Deadlocks. Angenommen, zwei gleichzeitige Prozesse (A & B) werden mit Tabellenvariablen ausgeführt, die (1, 2) und (2, 1) für dieselbe Firma enthalten.
Prozess A liest das Ziel, findet keine Zeile und fügt den Wert '1' ein. Es enthält eine exklusive Zeilensperre für den Wert '1'. Prozess B liest das Ziel, findet keine Zeile und fügt den Wert '2' ein. Es enthält eine exklusive Zeilensperre für den Wert '2'.
Jetzt muss Prozess A Zeile 2 verarbeiten, und Prozess B muss Zeile 1 verarbeiten. Keiner der Prozesse kann Fortschritte erzielen, da eine Sperre erforderlich ist, die nicht mit der exklusiven Sperre des anderen Prozesses kompatibel ist.
Um Deadlocks mit mehreren Zeilen zu vermeiden, müssen die Zeilen jedes Mal in derselben Reihenfolge verarbeitet (und auf Tabellen zugegriffen) werden . Die in der Frage gezeigte Tabellenvariable im Ausführungsplan ist ein Heap, daher haben die Zeilen keine intrinsische Reihenfolge (sie werden mit hoher Wahrscheinlichkeit in der Reihenfolge des Einfügens gelesen, obwohl dies nicht garantiert ist):
Das Fehlen einer konsistenten Zeilenverarbeitungsreihenfolge führt direkt zur Deadlock-Möglichkeit. Eine zweite Überlegung ist, dass das Fehlen einer Schlüssel-Eindeutigkeitsgarantie bedeutet, dass eine Tischspule erforderlich ist, um einen korrekten Halloween-Schutz zu gewährleisten. Der Spool ist ein eifriger Spool, dh alle Zeilen werden in eine Tempdb-Worktable geschrieben, bevor sie zurückgelesen und für den Einfügeoperator wiedergegeben werden.
Neudefinieren der
TYPE
Variablen der Tabelle, um ein Cluster einzuschließenPRIMARY KEY
:Der Ausführungsplan zeigt jetzt einen Scan des Clustered-Index und die Eindeutigkeitsgarantie bedeutet, dass das Optimierungsprogramm den Tabellenspool sicher entfernen kann:
In Tests mit 5000 Iterationen der
MERGE
Anweisung in 128 Threads traten keine Deadlocks mit der gruppierten Tabellenvariablen auf. Ich möchte betonen, dass dies nur auf der Grundlage von Beobachtungen geschieht. Die gruppierte Tabellenvariable könnte ihre Zeilen auch ( technisch ) in einer Vielzahl von Reihenfolgen produzieren, aber die Chancen auf eine konsistente Reihenfolge sind sehr stark erhöht. Das beobachtete Verhalten muss natürlich für jedes neue kumulative Update, Service Pack oder jede neue Version von SQL Server erneut getestet werden.Falls die Definition der Tabellenvariablen nicht geändert werden kann, gibt es eine andere Alternative:
Dadurch wird auch die Beseitigung der Spool (und der Konsistenz der Zeilenreihenfolge) auf Kosten der Einführung einer expliziten Sortierung erreicht:
Dieser Plan erzeugte auch keine Deadlocks unter Verwendung des gleichen Tests. Reproduktionsskript unten:
quelle
Ich denke, SQL_Kiwi hat eine sehr gute Analyse geliefert. Wenn Sie das Problem in der Datenbank lösen müssen, sollten Sie seinem Vorschlag folgen. Natürlich müssen Sie jedes Mal, wenn Sie ein Upgrade durchführen, ein Service Pack anwenden oder einen Index oder eine indizierte Ansicht hinzufügen / ändern, erneut testen, dass es weiterhin für Sie funktioniert.
Es gibt drei weitere Alternativen:
Sie können Ihre Einfügungen serialisieren, damit sie nicht kollidieren: Sie können sp_getapplock zu Beginn Ihrer Transaktion aufrufen und eine exklusive Sperre erwerben, bevor Sie MERGE ausführen. Natürlich musst du es noch auf Stress testen.
Sie können festlegen, dass ein Thread alle Ihre Einfügungen verarbeitet, sodass Ihr App-Server die Parallelität verarbeitet.
Sie können es nach Deadlocks automatisch wiederholen - dies ist möglicherweise der langsamste Ansatz, wenn die Parallelität hoch ist.
In beiden Fällen können nur Sie die Auswirkung Ihrer Lösung auf die Leistung bestimmen.
Normalerweise haben wir überhaupt keine Deadlocks in unserem System, obwohl wir ein großes Potenzial dafür haben. Im Jahr 2011 ist uns bei einer Bereitstellung ein Fehler unterlaufen, und innerhalb weniger Stunden traten ein halbes Dutzend Deadlocks auf, die alle demselben Szenario folgten. Ich habe das bald behoben und das war der letzte Schrei für das Jahr.
In unserem System verwenden wir meistens Ansatz 1. Es funktioniert wirklich gut für uns.
quelle
Ein anderer möglicher Ansatz - Ich habe festgestellt, dass Merge manchmal Probleme mit dem Sperren und der Leistung aufwirft. Es kann sich lohnen, mit der Abfrageoption Option (MaxDop x) zu spielen
In der dunklen und fernen Vergangenheit verfügte SQL Server über die Option zum Sperren auf Zeilenebene einfügen - dies scheint jedoch ein Todesfall zu sein, jedoch sollte eine Cluster-PK mit einer Identität dazu führen, dass die Einfügungen sauber ausgeführt werden.
quelle