it-swarm.com.de

Gleichzeitiges Einfügen und Löschen von SQL Server

Von: Problem mit gleichzeitiger Eingabe von SQL Server 2014

Zusatzfrage:
Wie löschen und fügen wir Zeilen in einer parallelen Multithreading-Umgebung wieder ein und vermeiden dabei Rennbedingungen, Deadlocks usw.? Verwenden wir noch (UPDLOCK, SERIALIZABLE) oder ein anderes Schloss?

Wir haben zwei Tabellen: Order und OrderLineDetail. Tabelle Order ist das übergeordnete Element, in dem allgemeine Informationen gespeichert werden. und OrderLineDetail ist die untergeordnete Tabelle, da Bestellungen mehrere Detailpositionen enthalten.

Bestellungen können aktualisiert werden. Überprüfen Sie daher zunächst, ob OrderId in der Tabelle vorhanden ist, und fügen Sie sie entsprechend ein oder aktualisieren Sie sie.

Aufgrund von Kundenänderungen, Leitungsproblemen, Serverbelegung usw. können die Bestelldateien mehrmals gesendet werden. Wir haben eine Zeitspalte Lastfiledatetime. Wenn der eingehende Zeitstempel älter ist, hat dies keine Auswirkungen auf die Tabelle.

Für die Tabelle OrderLineDetail haben wir manchmal keine natürlichen Schlüssel oder Ersatzschlüssel in unseren Dateien, daher löschen wir alle untergeordneten OrderLineDetail Elemente und füllen sie neu auf (dies ist ein älteres Legacy-System für XML-Dateien) ).

Create Table Orders 
(OrderId bigint primary key,  -- this is in xml files
 Lastfiledatetime datetime null
)

Create Table OrderLineDetail 
(OrderLineDetailid bigint primary key identity(1,1),  -- this is Not in the xml files
 OrderLineDescription varchar(50), 
 OrderLineQuantity int, 
 OrderId bigint foreign key references Orders(OrderId),
 Lastfiledatetime datetime
)

Wir arbeiten in einer Multithreading-Parallelverarbeitungsumgebung mit der SQL Server 2016-Stufe "Read Committed Snapshot Isolation".

Um das übergeordnete Element Order zu aktualisieren, gehen wir folgendermaßen vor:

BEGIN TRANSACTION;

IF NOT EXISTS
(
    SELECT * 
    FROM Order WITH (UPDLOCK, SERIALIZABLE)
    WHERE Order [email protected]
)
    INSERT INTO Order () VALUES ()
ELSE
    UPDATE Order
    SET ... 
    WHERE [email protected] 
    AND LastFileDatetime<@CreatedTime;

COMMIT TRANSACTION;

Neue Frage

Wie löschen und fügen wir die untergeordnete Tabelle OrderLineDetail in einer parallelen Multithreading-Umgebung ein und vermeiden dabei Rennbedingungen, Deadlocks usw.? Verwenden wir noch (UPDLOCK, SERIALIZABLE) oder ein anderes Schloss?

BEGIN TRANSACTION;

IF EXISTS
(
    SELECT * 
    FROM OrderLineDetail WITH (UPDLOCK, SERIALIZABLE)
    WHERE Order [email protected]
)
    DELETE OrderLineDetail
    WHERE [email protected]
    AND LastFileDatetime<@CreatedTime;

    INSERT INTO OrderLineDetail () VALUES ()

COMMIT TRANSACTION;
5
user129291

Beim Muster UPDLOCK, SERIALIZABLE Geht es darum, falsche Ergebnisse (einschließlich Fehler bei falschen Schlüsselverletzungen) aufgrund von Rennbedingungen zu vermeiden, wenn eine besonders häufige Operation ausgeführt wird, die als UPSERT - Aktualisiert eine vorhandene Zeile, falls vorhanden. Andernfalls fügen Sie eine neue Zeile ein.

... unter Vermeidung von Rennbedingungen, Deadlocks usw.?

Sie scheinen nach einer magischen Kombination von Hinweisen zu suchen, die es einem Datenbanksystem ermöglicht, sehr gleichzeitige Operationen ohne Konflikte auszuführen. Im Allgemeinen gibt es so etwas nicht. Die einzige Möglichkeit, Deadlocks vollständig zu vermeiden, besteht darin, immer Objekte in derselben Reihenfolge zu ändern.

Dies kann schwierig, sogar unmöglich in einer Fremdschlüsselbeziehung zu erreichen sein. Beachten Sie, dass Sie beim Einfügen eines neuen Objekts die übergeordnete Zeile vor den untergeordneten Zeilen hinzufügen müssen. Wenn Sie ein Objekt löschen, müssen Sie das Kind (die Kinder) vor dem Elternteil entfernen.

Dies bedeutet nicht, dass eine sorgfältige Verriegelung die Rennbedingungen (---) nicht verhindert , aber nicht garantieren kann, dass Deadlocks in allen Situationen vermieden werden.

Was die Frage betrifft, schützt yes mit dem Hinweis UPDLOCK, SERIALIZABLE Vor den Rennbedingungen UPSERT, aber Nein , Deadlocks werden nicht verhindert. Es kann sogar dazu beitragen, ihre Frequenz zu erhöhen. Dies ist im Voraus schwierig zu beurteilen, und selbst erfahrene SQL Server-Datenbankdesigner können etwas falsch machen.


Bei komplexeren Anforderungen mit mehreren Objekten besteht eine gängige Lösung darin, die übergeordnete Zeile vor dem Ändern der untergeordneten Elemente immer explizit ausschließlich zu sperren, selbst wenn die natürliche Reihenfolge darin besteht, das untergeordnete Element zuerst zu verarbeiten.

Zum Beispiel beim Löschen einer Bestellung:

DECLARE @OrderID bigint = 12345;

BEGIN TRANSACTION;

    -- EXTRA STEP
    -- Dummy update with XLOCK to exclusively lock the parent row
    UPDATE TOP (1) dbo.Orders WITH (XLOCK)
    SET Lastfiledatetime = NULL
    WHERE OrderId = @OrderID;

    -- Remove children
    DELETE dbo.OrderLineDetail
    WHERE OrderId = @OrderID;

    -- Remove parent
    DELETE dbo.Orders
    WHERE OrderId = @OrderID;

COMMIT TRANSACTION;

Der zusätzliche Schritt, die übergeordnete Zeile ausschließlich zu sperren, hilft dabei, Deadlocks zu beseitigen, wenn alle Änderungen derselben Änderungsreihenfolge folgen: Eltern zuerst, dann Kind (er). Wenn ein Prozess in umgekehrter Reihenfolge auf Objekte zugreift, besteht die Möglichkeit eines Deadlocks aufgrund inkompatibler Sperren.

Beachten Sie, dass dies wichtig sein kann, um tatsächlich etwas in der übergeordneten Zeile zu ändern, da SQL Server die exklusive Sperre (XLOCK) in einigen Situationen möglicherweise nicht einhält, wenn dies nicht der Fall ist notwendig für die auszuführende Operation.


Eine zweite Implementierung mit ungefähr derselben Idee ist die Verwendung von Anwendungssperren, wie in den Fragen und Antworten Implementieren von Anwendungssperren in SQL Server (Distributed Locking Pattern) (siehe die dortigen Dokumentationslinks) erwähnt.

Dies ist in mancher Hinsicht einfacher, aber auch hier müssen Sie sicherstellen, dass der gesamte Code, der die geschützten Objekte ändert, dasselbe Schema verwendet. Sobald irgendetwas auf die zugrunde liegenden Objekte zugreifen kann, ohne die erforderlichen Anwendungssperren zu verwenden, bricht das gesamte Schema zusammen.

Ein Beispiel unten zeigt, wie wir eine exklusive Anwendungssperre für eine bestimmte Bestellnummer vornehmen können, bevor wir Elemente der Bestellung verarbeiten:

BEGIN TRANSACTION;

    -- The order number we want exclusive access to
    DECLARE @OrderID integer = 12345;

    -- Compute the locking resource string
    DECLARE @Resource nvarchar(255) = N'dbo.Order' + CONVERT(nvarchar(11), @OrderID);

    -- Return code from sys.sp_getapplock
    DECLARE @RC integer;

    -- Build dynamic SQL
    DECLARE @SQL nvarchar(max) =
        N'
        EXECUTE @RC = sys.sp_getapplock
            @Resource = ' + QUOTENAME(@Resource) + N',
            @LockMode = Exclusive, 
            @LockTimeout = -1;'

    -- Try to acquire the lock
    EXECUTE sys.sp_executesql
        @SQL,
        N'@RC integer OUTPUT',
        @RC = @RC OUTPUT;

    -- Do something with the return code value if necessary
    SELECT @RC;

    --- Sensitive operations go here

    -- Release the application lock early if you can
    -- using sys.sp_releaseapplock

ROLLBACK TRANSACTION;

Parallelität ist schwer; Das tut mir leid.

8
Paul White 9

Und wenn ich die Antwort von Paul White verwende, habe ich zum Glück weniger Deadlock-Chancen. Ich werde die untergeordneten Orderlinedetails löschen, aber niemals die übergeordnete Order. Ich werde immer die übergeordnete Reihenfolge einfügen/ändern und dann die untergeordneten Auftragsdetails löschen und einfügen. Ich muss alle Transaktionen in dieser Reihenfolge ausführen.

Meine endgültige Lösung lautet also, alles in einer Transaktion zu behalten.

Das (UPDLOCK, SERIALIZABLE) LOCK wird sowohl zum Einfügen als auch zum Ändern in eine exklusive Sperre eskaliert (und Sperren können eskalieren, aber bei Transaktionen niemals deeskalieren). So kann ich die Order-Tabelle mit einer neuen exklusiven Sperre einfügen/ändern

Dann kann ich die untergeordneten Orderline-Details löschen und einfügen (wodurch auch exklusive Sperren erhalten werden).

Ich werde alles in dieser Reihenfolge halten. Alle anderen Transaktionen werden abgeschnitten, bis dies abgeschlossen ist, da die Transaktion exklusiv gesperrt ist.

BEGIN TRANSACTION;

IF NOT EXISTS
(
    SELECT * 
    FROM Order WITH (UPDLOCK, SERIALIZABLE)
    WHERE Order [email protected]
)
    INSERT INTO Order () VALUES ()
ELSE
BEGIN
    UPDATE Order
    SET ... 
    WHERE [email protected] 
    AND LastFileDatetime<@CreatedTime
END

DELETE OrderLineDetail
WHERE [email protected]
AND LastFileDatetime<@CreatedTime;

INSERT INTO OrderLineDetail () VALUES ()


COMMIT TRANSACTION;
1
user129291