Hallo LEUTE, ich nutze n8n mit Microsoft SQL Server, und mehrere Workflow-Ausführungen können gleichzeitig denselben Kundendatensatz aktualisieren.
Ich mache mir Sorgen um Race Conditions und verlorene Updates, wenn zwei Ausführungen denselben Datensatz lesen und dann versuchen, ihn zu aktualisieren.
UPDATE Customers
SET
Status = @status,
UpdatedAt = GETUTCDATE()
WHERE CustomerId = @customerId;
Würdet ihr Transaktionen, UPDLOCK/ROWLOCK, optimistische Concurrency mit einer rowversion-Spalte oder eine Kombination empfehlen? Und wie würdet ihr den n8n-Workflow strukturieren, damit ein fehlgeschlagenes oder gleichzeitiges Update sicher wiederholt werden kann, ohne neuere Daten zu überschreiben?
Problem/Fehler/Frage beschreiben
Welche Fehlermeldung wird angezeigt (falls vorhanden)?
Bitte teilt euren Workflow
(Wählt die Nodes auf eurer Canvas aus und nutzt die Tastenkombinationen CMD+C/CTRL+C und CMD+V/CTRL+V, um den Workflow zu kopieren und einzufügen.)
Verwenden Sie optimistisches Concurrency mit SQL Servers rowversion, besonders wenn mehrere n8n-Ausführungen denselben Datensatz aktualisieren können.
Fügen Sie eine rowversion-Spalte hinzu: ALTER TABLE Customers
ADD RowVersion ROWVERSION;
Wenn n8n den Kunden liest, behalten Sie die aktuelle RowVersion. Aktualisieren Sie den Datensatz dann nur, wenn diese Version noch unverändert ist: UPDATE Customers
SET
Status = @status,
UpdatedAt = SYSUTCDATETIME()
WHERE CustomerId = @customerId
AND RowVersion = @rowVersion;
Wenn das Update 0 Zeilen betrifft, hat ein anderer Workflow den Datensatz bereits geändert. n8n kann dann die neueste Version erneut lesen und entscheiden, ob ein Retry durchgeführt oder gestoppt werden soll.
Ich würde Transaktionen verwenden, wenn mehrere SQL-Operationen zusammen erfolgreich sein müssen, aber ich würde nicht überall Sperren hinzufügen.
Wenn zwei n8n-Ausführungen die gleiche RowVersion lesen und beide versuchen, den Kunden zu aktualisieren, wie würdest du die Ausführung handhaben, die 0 Zeilen aktualisiert? Würdest du automatisch mit der neuesten RowVersion erneut versuchen, oder sollte n8n stoppen und es als Concurrency-Konflikt kennzeichnen, um zu vermeiden, dass die Änderungen des anderen Workflows versehentlich überschrieben werden?
Ich würde den Datensatz nicht automatisch überschreiben. Wenn das Update 0 betroffene Zeilen zurückgibt, bedeutet das normalerweise, dass eine andere n8n-Ausführung den Datensatz zuerst geändert hat.
Ich würde es so handhaben:
UPDATE
↓
0 Zeilen betroffen?
↓
JA → Aktuellen Datensatz erneut lesen
↓
Änderungen vergleichen
↓
Nur erneut versuchen, wenn sicher
Lesen Sie zunächst die neueste Version:
SELECT
CustomerId,
Status,
RowVersion
FROM Customers
WHERE CustomerId = @customerId;
Führen Sie dann ein versiongeschütztes Update durch:
UPDATE Customers
SET
Status = @status,
UpdatedAt = SYSUTCDATETIME()
WHERE CustomerId = @customerId
AND RowVersion = @rowVersion;
Überprüfen Sie die Anzahl der betroffenen Zeilen. Wenn sie 0 ist, versuchen Sie nicht blind, dasselbe Update zu wiederholen. Rufen Sie den aktuellen Datensatz ab, vergleichen Sie die Änderungen und versuchen Sie nur mit der neuen RowVersion erneut, wenn das Update noch sicher ist.
Hallo Mary — Ich würde auch @Niffzy’s rowversion-Vorschlag als Standard wählen. Ein kleiner, aber wichtiger Unterschied: Eine Transaktion allein verhindert keinen verlorenen Update, und ROWLOCK ist nur ein Hinweis, daher würde ich keinen von beiden als Hauptmechanismus für die Korrektheit verwenden.
Ich würde das Update die neue Version zurückgeben lassen:
UPDATE dbo.Customers
SET
Status = @status,
UpdatedAt = SYSUTCDATETIME()
OUTPUT inserted.RowVersion
WHERE CustomerId = @customerId
AND RowVersion = @expectedRowVersion;
In n8n würde ich ein leeres Ergebnis / null betroffene Zeilen als Concurrency-Konflikt behandeln:
Die aktuelle Zeile erneut lesen.
Erneut bewerten, ob die angeforderte Statusänderung noch gültig ist.
Nur mit der neuen RowVersion wiederholen, mit einer kleinen maximalen Versuchsanzahl und Jitter.
Falls der Vorgang auch eine externe API aufruft, machen Sie diese Nebenwirkung idempotent, bevor Sie den Datenbankschritt wiederholen.
Eine zusätzliche Nuance: rowversion zeigt dir, dass sich die Datenbankzeile nach dem Lesen geändert hat, aber nicht, ob ein eingehendes Webhook-Ereignis geschäftlich gesehen neuer ist. Wenn die Quelle eine monotone Ereignisversion oder Sequenznummer bereitstellt, speichern Sie diese separat und fügen Sie etwas wie:
AND SourceVersion < @incomingSourceVersion
Ich würde auch verarbeitete Webhook-EventId-Werte hinter einer Unique Constraint speichern. rowversion schützt vor gleichzeitigen Schreibvorgängen; die Event-ID schützt vor doppelter Zustellung. Sie sind verwandt, aber leider lieben verteilte Systeme es, beide Arten von Problemen zu sammeln
Ich würde UPDLOCK/HOLDLOCK für eine kurze Transaktion mit mehreren abhängigen Anweisungen reservieren, die wirklich pessimistische Locks benötigen. Bei einem einzelnen Zeilenupdate ist optimistische Concurrency normalerweise einfacher und freundlicher zum Durchsatz.
Hi @Mary_Berry
Ein versionskontrolliertes UPDATE, das nichts trifft, ist immer noch eine erfolgreiche Abfrage, daher meldet der Microsoft-SQL-Node Erfolg und n8ns Einstellungen „Retry On Fail