簡體   English   中英

如何在 ETL 管道中正確截斷臨時表?

[英]How to properly truncate a staging table in an ETL pipeline?

我們有一個 ETL 管道,它為上傳到存儲帳戶 (Azure) 的每個 CSV 運行。 它在 CSV 上運行一些轉換並將輸出寫入另一個位置,也作為 CSV,並調用數據庫 (SQL Azure) 上的存儲過程,該過程將生成的 CSV 攝取 (BULK INSERT) 到一個臨時表中。

該管道可以同時執行,因為多個資源可以將文件上傳到存儲。 因此,臨時表經常插入數據。

然后,我們有一個計划的 SQL 作業(彈性作業),它觸發一個 SP,將數據從臨時表移動到最終表中。 此時,我們希望截斷/清空臨時表,以便我們不會在下次執行作業時重新插入它們。

問題是,我們無法確定在從暫存表加載到最終表和 truncate 命令之間,沒有任何新數據寫入暫存表可以在沒有先插入最終表的情況下被截斷。

有沒有辦法在我們將數據復制到最終表時鎖定臨時表,以便嘗試寫入它的 SP(從 ETL 管道調用)只會等到鎖定被釋放? 這是否可以通過使用事務或一些手動鎖定命令來實現?

如果沒有,處理這個問題的最佳方法是什么?

我喜歡sp_getapplock並且我自己在幾個地方使用這種方法,因為它的靈活性以及您可以完全控制鎖定邏輯和等待時間。

我看到的唯一問題是,在您的情況下,並發進程並不完全相同。

您有將數據從臨時表移動到主表的 SP1。 您的系統從不嘗試運行此 SP 的多個實例。

另一個將數據插入臨時表的 SP2可以同時運行多次,這樣做很好。

很容易實現鎖定,以防止 SP1 或 SP2 的任何組合的任何並發運行。 換句話說,如果 SP1 和 SP2 的鎖定邏輯相同並且它們被平等對待,則很容易。 但是,您不能同時運行多個 SP2 實例。

如何實現阻止 SP1 和 SP2 並發運行的鎖定,同時允許多個 SP2 實例同時運行,這一點並不明顯。


還有另一種方法不會試圖阻止 SP 的並發運行,而是包含並期望同時運行是可能的。

一種方法是將IDENTITY列添加到臨時表。 或者一個自動填充的日期時間,如果你能保證它是唯一的並且永遠不會減少,這可能很棘手。 rowversion列。

SP2 內部將數據插入臨時表的邏輯沒有改變。

SP1 內部將數據從臨時表移動到主表的邏輯需要使用這些標識值。

首先從臨時表中讀取身份的當前最大值並將其記住在一個變量中,例如@MaxID 來自該 SP1 中的臨時表的所有后續 SELECT、UPDATE 和 DELETE 應包括過濾器WHERE ID <= @MaxID

這將確保如果在 SP1 運行時碰巧有新行添加到臨時表中,則該行不會被處理並且會保留在臨時表中,直到 SP1 的下一次運行。

這種方法的缺點是你不能使用TRUNCATE ,你需要使用DELETEWHERE ID <= @MaxID


如果您同意多個 SP2 實例(和 SP1)相互等待,那么您可以使用類似於以下內容的sp_getapplock 我的存儲過程中有此代碼。 您應該將此邏輯放入 SP1 和 SP2。

我在這里沒有明確調用sp_releaseapplock ,因為鎖所有者設置為事務,引擎會在事務結束時自動釋放鎖。

您不必將重試邏輯放在存儲過程中,它可以在運行這些存儲過程的外部代碼中。 無論如何,您的代碼應該准備好重試。

CREATE PROCEDURE SP2  -- or SP1
AS
BEGIN
    SET NOCOUNT ON;
    SET XACT_ABORT ON;

    BEGIN TRANSACTION;
    BEGIN TRY
        -- Maximum number of retries
        DECLARE @VarCount int = 10;

        WHILE (@VarCount > 0)
        BEGIN
            SET @VarCount = @VarCount - 1;

            DECLARE @VarLockResult int;
            EXEC @VarLockResult = sp_getapplock
                @Resource = 'StagingTable_app_lock',
                -- this resource name should be the same in SP1 and SP2
                @LockMode = 'Exclusive',
                @LockOwner = 'Transaction',
                @LockTimeout = 60000,
                -- I'd set this timeout to be about twice the time
                -- you expect SP to run normally
                @DbPrincipal = 'public';

            IF @VarLockResult >= 0
            BEGIN
                -- Acquired the lock

                -- for SP2
                -- INSERT INTO StagingTable ...

                -- for SP1
                -- SELECT FROM StagingTable ...
                -- TRUNCATE StagingTable ...

                -- don't retry any more
                BREAK;
            END ELSE BEGIN
                -- wait for 5 seconds and retry
                WAITFOR DELAY '00:00:05';
            END;
        END;

        COMMIT TRANSACTION;
    END TRY
    BEGIN CATCH
        ROLLBACK TRANSACTION;
        -- log error
    END CATCH;

END

此代碼保證在任何給定時刻只有一個過程在使用臨時表。 沒有並發。 所有其他實例將等待。

顯然,如果您嘗試不通過這些 SP1 或 SP2(它們首先嘗試獲取鎖)來訪問臨時表,那么此類訪問將不會被阻止。

我會提出兩個相同的臨時表的解決方案。 讓我們將它們命名為 StageLoading 和 StageProcessing。
加載過程將有以下步驟:
1. 開始時兩個表都是空的。
2. 我們將一些數據加載到 StageLoading 表中(我假設每次加載都是一個事務)。
3. 當 Elastic 工作開始時,它會做:
- ALTER TABLE SWITCH 將所有數據從 StageLoading 移動到 StageProcessing。 它將使 StageLoading 為空並准備好進行下一次加載。 這是一個元數據操作,所以需要幾毫秒並且它是完全阻塞的,所以將在加載之間完成。
- 將 StageProcessing 中的數據加載到最終表格中。
- 截斷表 StageProcessing。
4. 現在我們准備好下一個 Elastic 工作了。

如果我們在 StageProcessing 不為空時嘗試執行 SWITCH,則 ALTER 將失敗,這意味着上次加載過程失敗。

有沒有辦法在我們將數據復制到最終表時鎖定臨時表,以便嘗試寫入它的 SP(從 ETL 管道調用)只會等到鎖定被釋放? 這是否可以通過使用事務或一些手動鎖定命令來實現?

看起來您正在尋找一種比事務級別更廣泛的機制。 SQL Server/Azure SQL DB 有一個,它被稱為應用程序鎖

sp_getapplock

鎖定應用程序資源。

放置在資源上的鎖與當前事務或當前會話相關聯。 當事務提交或回滾時,會釋放與當前事務關聯的鎖。 當會話被注銷時,與會話關聯的鎖被釋放。 當服務器因任何原因關閉時,所有鎖都會被釋放。

可以使用 sp_releaseapplock 顯式釋放鎖。 當應用程序為同一鎖資源多次調用 sp_getapplock 時,必須調用相同次數的 sp_releaseapplock 才能釋放鎖。 當使用事務鎖所有者打開鎖時,該鎖會在事務提交或回滾時釋放。

這基本上意味着您的 ETL 工具應該打開單個會話到數據庫,獲取鎖定並在完成后釋放。 其他會話在嘗試做任何事情之前應該嘗試獲取鎖(他們不能,因為它已經被占用了),等到它釋放並繼續工作。

假設您有一個出境工作

  • 將 OutboundProcessing BIT DEFAULT 0 添加到表中
  • 在作業中, SET OutboundProcessing = 1 WHERE OutboundProcessing = 0(聲明行)
  • 對於 ETL,在提供數據的查詢中合並 WHERE OutboundProcessing = 1(傳輸行)
  • 在 ETL 之后,DELETE FROM TABLE WHERE OutboundProcessing = 1(刪除您傳輸的行)
  • 如果 ETL 失敗,則 SET OutboundProcessing = 0 WHERE OutboundProcessing = 1

我總是喜歡“識別”我收到的每個文件。 如果可以這樣做,則可以在整個加載過程中關聯給定文件中的記錄。 你沒有提出需要這個,但只是說。

但是,每個文件都有一個標識(應該只使用 int/bigint 標識值),然后您可以從“模板”加載表動態創建任意數量的加載表。

  1. 當文件到達時,創建一個以文件 ID 命名的新加載表。
  2. 處理從加載到最終表的數據。
  3. 刪除正在處理的文件的加載表。

這有點類似於使用 2 個表(加載和暫存)的其他解決方案,但即使在該解決方案中,您仍然只能“加載”2 個文件(盡管您仍然只將一個文件應用於最終表?)

最后,不清楚您的“彈性作業”是否與實際的“加載”管道/處理分離或是否包含在內。 作為一項工作,我認為它不包括在內,如果是一項工作,您一次只能運行一個實例? 因此,如果您一次只能將一個文件從 load 移動到 final,那么為什么一次加載多個文件很重要就不清楚了。 為什么急於加載文件?

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM