2010-04-09 109 views
4

我有我幾乎總是遵循一個模式,在這裏,如果我需要在交易收官的操作,我這樣做:後如何避免在嵌套存儲過程中的嵌套事務中使用重複的保存點名稱?

BEGIN TRANSACTION 
SAVE TRANSACTION TX 

-- Stuff 

IF @error <> 0 
    ROLLBACK TRANSACTION TX 

COMMIT TRANSACTION 

那服務是我不夠好,在過去,但多年的使用這個模式(和複製上面的代碼),我突然發現了一個完全衝擊的缺陷。

很多時候,我將有一個存儲過程調用其他存儲過程,所有這些過程都使用相同的模式。我發現(以我的成本)是因爲我在任何地方都使用相同的保存點名稱,所以我可以進入一個情況,即我的外部事務部分提交 - 恰恰與我試圖實現的原子性相反。

我已經彙集了一個展示問題的例子。這是一個單獨的批處理(沒有嵌套的存儲過程),所以它看起來有點奇怪,因爲你可能不會在同一批次中使用同一個保存點名稱兩次,但是我的真實世界的場景太容易發佈。

CREATE TABLE Test (test INTEGER NOT NULL) 

BEGIN TRAN 
SAVE TRAN TX 

    BEGIN TRAN 
    SAVE TRAN TX 
     INSERT INTO Test(test) VALUES (1) 
    COMMIT TRAN TX 

    BEGIN TRAN 
    SAVE TRAN TX 
     INSERT INTO Test(test) VALUES (2) 
    COMMIT TRAN TX 

    DELETE FROM Test 

ROLLBACK TRAN TX 
COMMIT TRAN TX 

SELECT * FROM Test 

DROP TABLE Test 

當我執行此操作時,它列出了一個值爲「1」的記錄。換句話說,即使我回滾了外部交易,,表中也添加了一條記錄。

發生什麼事是在外層的ROLLBACK TRANSACTION TX回滾到內層的最後SAVE TRANSACTION TX。現在我寫完了這些,我可以看到它背後的邏輯:服務器正在回顧日誌文件,將其視爲線性事務流;它不理解嵌套事務(或者在我的真實世界場景中,通過調用其他存儲過程)所隱含的嵌套/層次結構。

因此,顯然,我需要開始使用唯一的保存點名稱,而不是盲目地使用「TX」。但是 - 這是我終於明白的地方 - 是否有辦法以可複製的方式執行此操作,以便我可以在任何地方使用相同的代碼?我能以某種方式自動生成保存點名稱嗎?做這種事情是否有慣例或最佳做法?

每次開始交易時都不難想出一個獨一無二的名字(可以根據SP名稱或其他名稱),但我確實擔心最終會出現衝突 - 而且您不會這樣做,知道這件事,因爲,而不是導致其只是默默地破壞你的數據的錯誤... :-(

回答

1

看看文檔:SAVE TRANSACTION (Transact-SQL)

SAVE { TRAN | TRANSACTION } { savepoint_name | @savepoint_variable } 
[ ; ] 

貌似可以基於一個變量的名字,所以請試着製作您的圖案:

DECALRE @savepoint_variable varchar(1000) 
SET @savepoint_variable=OBJECT_NAME(@@PROCID)+'|'+CONVERT(char(23),GETDATE(),121) 

BEGIN TRANSACTION 
SAVE TRANSACTION @savepoint_variable 

-- Stuff 

IF @error <> 0 
BEGIN 
    ROLLBACK TRANSACTION @savepoint_variable 
END 

COMMIT TRANSACTION 

當從不同的過程調用時,您的@savepoint_variable將具有不同的本地值,並且您的回滾應該回滾正確。我在保存點名稱中放入當前日期時間,因爲您可能在某個時間點使用遞歸,如果這是複製粘貼模式,則最好處理所有情況。

+0

如果您進一步閱讀本答案頂部的鏈接,您會看到「@savepoint_variable ...超過32個字符可以傳遞給變量,但只會使用前32個字符「。這意味着你的CONVERT(char(23),GETDATE(),121)很可能會被截斷,直到你的保存點名稱(以及可能的程序名稱結束)。 – Lee 2016-08-24 19:16:43

2

同意KM的解決方案。

我更喜歡使用GUID來生成唯一的保存點名稱。

DECLARE @savepoint AS VARCHAR(36) 
SET @savepoint = CONVERT(VARCHAR(36), NEWID()) 

BEGIN TRANSACTION 
SAVE TRANSACTION @savepoint 
... 

ROLLBACK TRANSACTION @savepoint 
COMMIT TRANSACTION 
+0

是的,我可能更喜歡這種方式,因爲它看起來不像醜陋的代碼,並且無論如何也沒有真正的好處,因爲你永遠不會看到它們。 – 2010-04-10 08:21:39

+0

查看我對KM的回覆。 @savepoint中任何超過32的字符都將被忽略,並留下一個截斷的GUID。 – Lee 2016-08-24 19:18:45

+1

NEWID()是36個字符長。其中恰好有4個破折號。如果您替換(NEWID(),' - ',''),您將得到完全32個字符並保留GUID的唯一性。 :) – Lee 2016-08-24 19:32:14