为什么SQL事务会自动回滚?回滚超过预期?

时间:2021-05-27 18:55:38

标签: sql-server transactions ssms sql-server-2014

我有一个很长的 SQL 脚本,我通过在 SSMS 中逐个语句执行它来测试它。我正在本地机器上的数据库上进行测试。

脚本中没有“外部”事务或嵌套事务。

精简示例:

USE NameOfDatabase

-- rename a table (outside a transaction)
EXEC sp_rename 'Table_XYZ', 'TableZ';

-- add a column to a table (outside a transaction)
ALTER TABLE TableB
ADD NewCol {...}

根据输出选项卡,所有这些语句都成功执行。

在脚本的很多地方,我都使用了 BEGIN TRANSACTION/COMMIT TRANSACTION,中间只有几条语句。所有这些似乎都按预期工作,因为我在执行 Command(s) completed successfully 语句时收到消息“COMMIT TRANSACTION”。

示例:

-- create table statement outside a transaction
CREATE TABLE NewTableC {...}

-- inserting data from a table to be dropped, and dropping the table
BEGIN TRANSACTION

    INSERT INTO NewTableC {...}
    SELECT {...} FROM ObsoleteTableC
    
    DROP TABLE ObsoleteTableC

COMMIT TRANSACTION    -- "Command(s) completed successfully"

-- another smaller transaction
BEGIN TRANSACTION

    -- Table HasFK_A contains the foreign key constraint 'FK_A_ToBigTable'
    DROP TABLE HasFK_A

COMMIT TRANSACTION    -- "Command(s) completed successfully"

然后我的脚本中有一个特别大的事务,我在其中创建一个新表、插入数据、删除约束、删除表等。

示例:

BEGIN TRANSACTION

    -- create replacement table with temporary name
    CREATE TABLE Tmp_BigTable {...}

    -- insert data
    INSERT INTO Tmp_BigTable {...}
    SELECT {...} FROM BigTable

    -- drop constraints to old table
    -- Table 'HasFK_A' and its constraint 'FK_A_ToBigTable' should've already been dropped in a previous transaction
    ALTER TABLE HasFK_B DROP CONSTRANT FK_B_ToBigTable
    ALTER TABLE HasFK_C DROP CONSTRANT FK_C_ToBigTable

    -- drop old table
    DROP TABLE BigTable    -- error, there are still foreign keys referencing the table

    -- rename new table
    EXEC sp_rename 'Tmp_BigTable', 'BigTable'

    -- re-add constraints
    ALTER TABLE HasFK_B DROP CONSTRANT FK_B_ToBigTable
    ALTER TABLE HasFK_C DROP CONSTRANT FK_C_ToBigTable

-- commit transaction
COMMIT TRANSACTION

当我在那个更大的事务中执行 DROP TABLE BigTable 语句时,我收到一个错误,指出仍然有一个引用表的外键。

我打开了一个新的查询窗口并检查了 sys.foreign_keys 以查看我遗漏了什么。令人惊讶的是,我在之前的较小事务中已经删除或重命名的表中列出了外键,这些表似乎已成功提交。我很快为所有剩余的外键编写了 ALTER TABLE 语句,并执行了它们。他们都没有出错,我认为这意味着那些“删除”和“重命名”的表仍然存在于数据库中。

我检查了 DROP TABLE BigTable 语句现在是否会运行,因为我删除了额外的外键。它再次失败 - sys.foreign_keys 中的其余键现在是我最初在脚本中拥有的键 (FK_B_ToBigTable, FK_C_ToBigTable)。这让我相信第一个错误是事务回滚时。

我删除了这些约束,然后是 BigTable,以确保已捕获所有外键。这一次,BigTable 掉线成功。我确实尝试通过重命名不存在的新表 (Tmp_BigTable) 并提交事务来确认事务已回滚,但由于没有打开的事务而失败。

令人困惑的是,事务似乎回滚到了“大”事务的范围之外。被删除的表似乎仍然存在,被重命名的表似乎有它们的旧名称。

我的问题是:

  1. 为什么在抛出错误时事务会自动回滚?

每个MSDN

<块引用>

如果批处理中出现运行时语句错误(例如违反约束),数据库引擎中的默认行为是仅回滚生成错误的语句 。您可以使用 SET XACT_ABORT 语句更改此行为。

我的脚本中没有任何 SET XACT_ABORT 语句。

  1. 为什么在执行 COMMIT 语句时回滚(或从未提交)显示“命令成功完成”的更改?

编辑:我已经通过脚本找到了语句似乎已回滚到的点。看来我可能不小心忘记执行其中一个较小交易的 COMMIT 语句。

那么实际执行的内容是这样的:

BEGIN TRANSACTION
    UPDATE ...
    EXEC sp_rename ...
    /* missed COMMIT TRANSACTION */
    
    ALTER TABLE ...
    UPDATE ...
    CREATE TABLE ...
    
    BEGIN TRANSACTION
        INSERT ...
        DROP TABLE ...
    COMMIT TRANSACTION
    
    BEGIN TRANSACTION
        ALTER TABLE {NewName} DROP CONSTRANT ...
        DROP TABLE ...
        
-- error on DROP TABLE rolls back both inner and outer nested transactions
ROLLBACK TRANSACTION        

SELECT * FROM sys.foreign_keys    -- returns foreign keys as they looked before "outer" transaction

这样就可以回答问题 2,而不是问题 1。

0 个答案:

没有答案
相关问题