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

Gar*_*ill 5 sql t-sql sql-server transactions

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

BEGIN TRANSACTION
SAVE TRANSACTION TX

-- Stuff

IF @error <> 0
    ROLLBACK TRANSACTION TX

COMMIT TRANSACTION
Run Code Online (Sandbox Code Playgroud)

这在过去对我很有帮助,但是在使用这种模式多年后(并复制粘贴上述代码),我突然发现了一个完全令人震惊的缺陷。

很多时候,我会有一个调用其他存储过程的存储过程,所有这些存储过程都使用相同的模式。我发现(以我的成本为代价)是因为我在任何地方都使用相同的保存点名称,所以我可能会遇到我的外部事务部分提交的情况 - 恰恰与我试图实现的原子性相反.

我已经整理了一个展示问题的例子。这是一个单一的批次(没有嵌套的存储过程),因此看起来有点奇怪,因为您可能不会在同一批次中两次使用相同的保存点名称,但我的真实世界场景太混乱而无法发布。

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
Run Code Online (Sandbox Code Playgroud)

当我执行此操作时,它会列出一条记录,值为“1”。换句话说,即使我回滚了外部事务,表中还是添加了一条记录。

发生的事情是ROLLBACK TRANSACTION TX外层的回滚SAVE TRANSACTION TX到内层的最后一个。现在我写完了这一切,我可以看到它背后的逻辑:服务器正在查看日志文件,将其视为线性事务流;它不理解事务嵌套(或者,在我的实际场景中,通过调用其他存储过程)隐含的嵌套/层次结构。

所以,很明显,我需要开始使用唯一的保存点名称,而不是到处盲目使用“TX”。但是-这是我终于切入正题-有没有办法做到这一点的复制pastable方式,以便我仍然无处不在使用相同的代码?我可以以某种方式动态自动生成保存点名称吗?是否有做这种事情的约定或最佳实践?

每次开始交易时想出一个唯一的名称并不难(可以基于 SP 名称或其他名称),但我确实担心最终会发生冲突 - 而你不会知道它因为它不会引起错误,而是默默地破坏您的数据...... :-(

KM.*_*KM. 2

查看文档:保存事务(Transact-SQL)

SAVE { TRAN | TRANSACTION } { savepoint_name | @savepoint_variable }
[ ; ]
Run Code Online (Sandbox Code Playgroud)

看起来你可以根据变量命名它,所以尝试制作你的模式:

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
Run Code Online (Sandbox Code Playgroud)

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