sql-server - "Nested"交易,为什么会出现这种情况,如何避免?

标签 sql-server t-sql transactions

demonstrated here ,

如果我有一个内部存储过程,它总是会回滚而不会出现错误。

CREATE PROCEDURE [inner] AS BEGIN
    SET XACT_ABORT, NOCOUNT ON;
    
    BEGIN TRY
        BEGIN TRANSACTION;
        
        IF (1 + 1 = 2) BEGIN
            ROLLBACK TRANSACTION;
        END ELSE BEGIN
            COMMIT TRANSACTION;
        END
    END TRY
    BEGIN CATCH
        IF @@TRANCOUNT > 0 BEGIN
            ROLLBACK TRANSACTION;
        END
    END CATCH
END

以及一个调用内部 SP 的外部 SP,

CREATE PROCEDURE [outer] AS BEGIN
    SET XACT_ABORT, NOCOUNT ON;
    
    BEGIN TRY
        BEGIN TRANSACTION;
        
        EXEC [inner];
    
        COMMIT TRANSACTION;
    END TRY
    BEGIN CATCH
        IF @@TRANCOUNT > 0 BEGIN
            ROLLBACK TRANSACTION;
        END
        
        ;THROW;
    END CATCH
END

然后我调用外部 SP,

EXEC [outer];

我收到此错误

Msg 266 Level 16 State 2 Line 0
Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements.
Previous count = 1, current count = 0.

现在,最初这并不是我所期望的。

我认为正在发生的事情,

公理:

  • BEGIN TRANSACTION 始终将 @@TRANCOUNT 加 1;
  • COMMIT TRANSACTION 始终将 @@TRANCOUNT 减 1;
  • ROLLBACK TRANSACTION 始终将 @@TRANCOUNT 重置为 0;
  • 没有嵌套事务!
  • @@TRANCOUNT 更改为除 0 之外的任何值时,不会发生任何其他情况。

所以,一步一步,

  1. [outer] -> BEGIN TRANSACTION@@TRANCOUNT 设置为 1
  2. [outer] -> 使用 @@TRANCOUNT 1 调用 [inner]。
  3. [inner] -> ROLLBACK TRANSACTION@@TRANCOUNT 重置为 0
  4. [inner] -> 使用 @@TRANCOUNT 0 返回到 [outer]。
  5. SQL Server 在调用 [inner] 之前和之后检测到 @@TRANCOUNT 不匹配,并且, 因此会引发上述错误。

所以,我的问题是,

我的理解正确吗?规范文档在哪里?

编写可能想要回滚自己的更改的存储过程的最佳方法是什么,以便可以在自己的批处理中或从另一个事务中直接调用它?

最佳答案

是的,您的理解是正确的。

The documentation is here:

In stored procedures, ROLLBACK TRANSACTION statements without a savepoint_name or transaction_name roll back all statements to the outermost BEGIN TRANSACTION. A ROLLBACK TRANSACTION statement in a stored procedure that causes @@TRANCOUNT to have a different value when the stored procedure completes than the @@TRANCOUNT value when the stored procedure was called produces an informational message. This message does not affect subsequent processing.

我不知道为什么它说“不影响后续处理”,因为那是 demonstrably false :相反,它会抛出严重性为 16 的错误,可以使用 BEGIN CATCH 捕获该错误。


如果您确实想使用这样的嵌套事务,并且只能部分回滚它们,那么它会变得更加复杂。

如果没有,您必须有条件地开始交易。然后你必须SAVE一个保存点。然后每次回滚必须有条件地回滚整个事务或仅回滚保存点

CREATE PROCEDURE [inner] AS BEGIN
    SET XACT_ABORT, NOCOUNT ON;
    
    BEGIN TRY
        DECLARE @tranCount int = @@TRANCOUNT;
        IF @tranCount = 0
            BEGIN TRANSACTION;

        SAVE TRANSACTION innerSave;

        IF (1 + 1 = 2) BEGIN
            IF @tranCount = 0
                ROLLBACK;
            ELSE
                ROLLBACK TRANSACTION innerSave;
        END ELSE BEGIN
            COMMIT TRANSACTION;
        END
    END TRY
    BEGIN CATCH
        IF @@TRANCOUNT > 0 BEGIN
            IF @tranCount = 0
                ROLLBACK;
            ELSE
                ROLLBACK TRANSACTION innerSave;
        END
    END CATCH
END

db<>fiddle

关于sql-server - "Nested"交易,为什么会出现这种情况,如何避免?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/73400364/

相关文章:

sql-server - CSV 中的 T-SQL OpenRowSet 缺少标题行

java - 如何在 Java EE 应用程序中锁定数据库记录?

java - H2 是否支持可序列化隔离级别?

sql - 对组中上次更新的数据求和

sql-server - NewID() 函数的说明

SQL 服务器 2008 : BEGIN TRY and BEGIN TRANSACTION in CURSOR

sql-server - 如何从 SQL Server 数据库检索值?

sql - 如何使用基于多个重复列的序列自动对重复行进行编号 (T-SQL)

php - 在事务中更新不同的数据库(Laravel)

SQL 包含 - 仅在开始时匹配