如何避免在嵌套存储过程中的嵌套事务中使用重复的保存点名称?

时间:2010-04-09 10:15:31

标签: sql sql-server tsql transactions

我有一个我几乎总是遵循的模式,如果我需要在事务中结束操作,我会这样做:

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名称,或者某些事情),但我确实担心最终会发生冲突 - 你不会知道它,因为它不是导致错误而是默默地破坏你的数据......: - (

2 个答案:

答案 0 :(得分:2)

同意KM的解决方案。

我更喜欢使用GUID来生成唯一的保存点名称。

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

BEGIN TRANSACTION
SAVE TRANSACTION @savepoint
...

ROLLBACK TRANSACTION @savepoint
COMMIT TRANSACTION

答案 1 :(得分:1)

查看文档:{​​{3}}

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将具有不同的本地值,并且你的回滚应该回滚正确的。我在保存点名称中放入当前日期时间,因为您可能在某些时候使用递归,如果这是复制粘贴模式,则最好处理所有情况。