【问题标题】:Transaction does not rollback all changes事务不会回滚所有更改
【发布时间】:2019-05-29 23:24:28
【问题描述】:

我在 SQL Server 2017 中遇到了一个过程,该过程在 try-catch 块内有一个事务。它不是嵌套的,只是使用游标填充和循环的标识表。所以try-catch 在一个循环中,调用了其他一些过程。有时该过程会因违反约束错误而失败,并且在内部异常之前保存成功的任何内容都很好。然后我在 catch 子句中遇到了 commit。这让我想知道,我写了这段代码:

DECLARE @Table TABLE (ID INT NOT NULL PRIMARY KEY)
DECLARE @Input TABLE (ID INT)

INSERT INTO @Input 
VALUES (1), (1), (2), (NULL), (3)

DECLARE @Output TABLE (ID INT)

--SET XACT_ABORT OFF

DECLARE @ID int

DECLARE [Sequence] CURSOR LOCAL FAST_FORWARD FOR
    SELECT ID FROM @Input

OPEN [Sequence]

FETCH NEXT FROM [Sequence] INTO @ID

WHILE @@FETCH_STATUS = 0
BEGIN
    BEGIN TRY
        BEGIN TRAN

        DECLARE @Msg nvarchar(max) = 'Inserting '''  + TRY_CAST(@ID as varchar(11)) + ''''
        RAISERROR (@Msg, 0, 0) WITH NOWAIT

        -- Order is important
        --INSERT INTO @Table VALUES (@ID)
        INSERT INTO @Output VALUES (@ID)
        INSERT INTO @Table VALUES (@ID)

        COMMIT TRAN
    END TRY
    BEGIN CATCH
        SET @Msg = 'Caught ' + CAST(ERROR_NUMBER() as varchar(11)) + ' : ' + ERROR_MESSAGE()
        RAISERROR (@Msg, 1, 1) WITH NOWAIT
        IF XACT_STATE() = -1
        BEGIN
            SET @Msg = 'Uncommitable transaction [-1]'
            RAISERROR (@Msg, 1, 1) WITH NOWAIT
            ROLLBACK TRAN
        END
        IF XACT_STATE() = 1
        BEGIN
            SET @Msg = 'Commitable transaction [1]'
            RAISERROR (@Msg, 1, 1) WITH NOWAIT
            COMMIT TRAN
        END
    END CATCH
    FETCH NEXT FROM [Sequence] INTO @ID
END

SELECT * FROM @Table
SELECT * FROM @Output

所以当我尝试交换 @Output@Table 插入的顺序时,我得到了不同的结果,无论 XACT_ABORT 设置为什么,或者我是否在捕捉块。我一直很确定,一切都会回滚,@Output@Table 表将相等....

我在这里做错了什么?这是默认的交易行为吗?

【问题讨论】:

  • 如果一切正常,你就提交。仅当事务不可提交时才尝试回滚。如果它是可提交的但抛出了错误怎么办?触发 CATCH 的错误并不总是意味着事务状态将为 -1。例如,如果您在 COMMIT TRAN 之前放置一个 SELECT 1/0,您将触发 catch 块,但事务会非常好。

标签: sql-server transactions try-catch rollback


【解决方案1】:

正如Ben Thul 所提醒的,这里只能使用临时表或普通表。因此,当捕获到异常并且XACT_STATE() = 1(可提交事务)时,COMMIT 将保留任何成功,ROLLBACK 将撤消整个事情。

    IF XACT_STATE() = 1
    BEGIN
        SET @Msg = 'Commitable transaction [1]'
        RAISERROR (@Msg, 1, 1) WITH NOWAIT
        COMMIT TRAN  -- Keep changes or undo everything (ROLLBACK)
    END

输出表结果:
ROLLBACK: [1,2,3]
COMMIT: [1,1,2,NULL,3]

【讨论】:

    【解决方案2】:

    这很有趣,但您的代码符合我的预期。表变量不遵守事务语义。不过临时桌子可以!因此,如果您需要能够将突变回滚到您的临时“事物”,请使用表而不是变量。

    请注意,尽管您的序列仍会从中提取值。即使你也把它放在交易中。

    【讨论】:

      猜你喜欢
      • 2017-02-16
      • 1970-01-01
      • 1970-01-01
      • 2015-06-25
      • 1970-01-01
      • 2021-04-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多