嗨夥伴們 我在使用 n8n 搭配 Microsoft SQL Server,多個工作流程執行可能幾乎同時更新同一筆客戶記錄。
我擔心當兩個執行緒讀取相同的記錄,然後試圖更新它時,會發生競態條件和遺失更新的問題。
UPDATE Customers
SET
Status = @status,
UpdatedAt = GETUTCDATE()
WHERE CustomerId = @customerId;
你會建議使用交易、UPDLOCK/ROWLOCK、使用 rowversion 欄的樂觀並行控制,還是組合使用呢?另外,你會如何結構化 n8n 工作流程,使得失敗或並行的更新可以安全地重試,而不會覆寫較新的資料呢?
描述問題/錯誤/問題
錯誤訊息是什麼(如有的話)?
請分享您的工作流程
(在畫布上選擇節點,使用鍵盤快捷鍵 CMD+C/CTRL+C 和 CMD+V/CTRL+V 來複製和貼上工作流程。)
分享最後一個節點返回的輸出
有關您的 n8n 設定的資訊
- n8n 版本:
- 資料庫(預設:SQLite):
- n8n EXECUTIONS_PROCESS 設定(預設:own、main):
- 執行 n8n 的方式(Docker、npm、n8n cloud、桌面應用程式):
- 作業系統:
嗨 @Mary_Berry
在 SQL Server 的 rowversion 中使用樂觀並行控制,特別是當多個 n8n 執行可以更新同一條記錄時。
新增一個 rowversion 資料行:ALTER TABLE Customers
ADD RowVersion ROWVERSION;
當 n8n 讀取客戶時,保留目前的 RowVersion。然後只在該版本仍未變更時更新記錄:UPDATE Customers
SET
Status = @status,
UpdatedAt = SYSUTCDATETIME()
WHERE CustomerId = @customerId
AND RowVersion = @rowVersion;
如果更新影響 0 列,表示另一個工作流程已經變更了該記錄。n8n 可以重新讀取最新版本,並決定是否重試或停止。
當多個 SQL 操作需要一起成功時,我會使用交易,但我不會到處新增鎖定。
感謝 @Niffzy 的回覆
如果兩個 n8n 執行讀取相同的 RowVersion 並都嘗試更新客戶,你會如何處理獲得 0 列更新的執行?你會用最新的 RowVersion 自動重試,還是應該讓 n8n 停止並將其標記為並行衝突,以避免意外覆蓋另一個工作流程的更改?
我不會自動覆寫記錄。如果更新傳回 0 列受影響,通常表示另一個 n8n 執行先改變了記錄。
我會像這樣處理:UPDATE
↓
0 列受影響?
↓
是 → 重新讀取最新記錄
↓
比較變更
↓
僅在安全時重試
首先,讀取最新版本:SELECT
CustomerId,
Status,
RowVersion
FROM Customers
WHERE CustomerId = @customerId;
然後執行版本保護更新:UPDATE Customers
SET
Status = @status,
UpdatedAt = SYSUTCDATETIME()
WHERE CustomerId = @customerId
AND RowVersion = @rowVersion;
檢查受影響的列數。如果是 0,不要盲目重試相同的更新。取得最新記錄、比較變更,並且僅在更新仍然安全的情況下才使用新的 RowVersion 重試。
嗨 Mary — 我也會選擇 @Niffzy 的 rowversion 建議作為預設值。有一個小但重要的區別:交易本身並不能防止遺失更新,而且 ROWLOCK 只是一個提示,所以我不會將任何一個作為主要的正確性機制。
我會讓更新傳回新版本:
UPDATE dbo.Customers
SET
Status = @status,
UpdatedAt = SYSUTCDATETIME()
OUTPUT inserted.RowVersion
WHERE CustomerId = @customerId
AND RowVersion = @expectedRowVersion;
在 n8n 中,我會將空結果 / 零受影響的行視為並行衝突:
- 再次讀取最新的行。
- 重新評估所請求的狀態變更是否仍然有效。
- 只使用新的
RowVersion 重試,使用較小的最大嘗試次數和抖動。
- 如果操作還呼叫外部 API,在重試資料庫步驟之前,先使該副作用具有冪等性。
一個額外的細微差別:rowversion 告訴你資料庫行在你讀取它之後已變更,但它不會告訴你傳入的 webhook 事件在業務術語中是否更新。如果來源提供單調遞增的事件版本或序列號,請分別儲存,並新增類似以下內容:
AND SourceVersion < @incomingSourceVersion
我也會在唯一約束後保持已處理的 webhook EventId 值。rowversion 保護防止並行寫入;事件 ID 保護防止重複傳遞。它們相關聯,但遺憾的是分散式系統喜歡同時收集這兩種麻煩 
我會保留 UPDLOCK/HOLDLOCK 用於包含多個相依陳述式的短交易,這些陳述式確實需要悲觀鎖定。對於單一行更新,樂觀並行控制通常更簡單,也對輸送量更友善。
嗨 @Mary_Berry
版本防護的 UPDATE 即使沒有符合任何項目仍然是成功的查詢,所以 Microsoft SQL 節點會報告成功,n8n 的「失敗時重試」和「出錯時」設定永遠不會在衝突時啟動。「失敗時重試」根本就不是正確的工具,因為它會用相同的過時預期版本重新執行同一個節點。改為結構化為顯式迴圈:IF 節點檢查返回的行數,衝突分支連接回重新讀取節點,以及迴圈中的計數器來限制嘗試次數。
該節點有一個細節,它的查詢參數是位置性的,並以 $1、$2、$3 的順序被引用,而不是被命名為 @variables。