【问题标题】:T-SQL could not rollbackT-SQL 无法回滚
【发布时间】:2011-08-10 12:18:14
【问题描述】:

我有一些纯顺序流的代码,没有事务。 我将它们与开始事务和提交事务夹在中间

begin transaction

......--My code here......
......
......--code to create Table1
......
ALTER TABLE [dbo].[Table1]  WITH CHECK ADD  CONSTRAINT [FK_constraint] FOREIGN KEY([field1], [field2])
REFERENCES [dbo].[Table2] ([field3], [field4])
GO
....
......--End of My code here......


rollback transaction
commit transaction

当我在管理工作室中运行脚本直到刚好在“回滚事务”上方时,如果发生简单的错误,例如被零除,我运行“回滚事务”,所有更改都回滚没有问题。

但如果由于 Table2 不存在而导致 alter table 语句失败,则会触发更多错误。

消息 1767,第 16 级,状态 0,第 2 行 外键 'FK_Constraint 引用了无效的表 'dbo.Table2'。

消息 1750,第 16 级,状态 0,第 2 行 无法创建约束。查看以前的错误。

消息 1767,第 16 级,状态 0,第 2 行 外键“FK_xxxxxx”引用了无效的表“Table1”。

当我运行“回滚事务”时,我收到此错误消息“回滚事务请求没有相应的 BEGIN TRANSACTION。”这很愚蠢,因为我确实有一个开始交易!

请告诉我出了什么问题。任何帮助将非常感激。使用 SQL-Server 2008。

编辑:

我加了

SELECT @@TRANCOUNT;

“ALTER TABLE [dbo].[Table1] WITH CHECK ADD CONSTRAINT”之前和之后

....
SELECT @@TRANCOUNT;
ALTER TABLE [dbo].[Table1]  WITH CHECK ADD  CONSTRAINT [FK_constraint] FOREIGN KEY([field1], [field2]) REFERENCES [dbo].[Table2] ([field3], [field4])
GO 
SELECT @@TRANCOUNT;
....

结果分别为 1 和 0。发生错误时,alter table 会自动回滚我的事务!?这个我看不懂。

【问题讨论】:

  • 听起来您正在手动运行各个代码段。在第二组错误发生之前,您是否可能没有突出显示 begin transaction 部分?
  • 是的,我正在手动运行它们。我确实在每次运行开始时突出显示了开始事务。
  • 我想,那种DDL的异常,Sql Server会自动为你回滚事务;但真正令人失望的是它不会将控制权交还给您,您的代码无法执行优雅的执行,例如您的代码将无法检测到事务级别(通过@@TranCount

标签: sql tsql sql-server-2008


【解决方案1】:

我认为对于使用 DDL 错误严重性处理的 Sql Server 处理,您无能为力,其中一些是由 Sql Server 本身自动处理的(例如强制回滚事务)。

您可以做的是让您的脚本代码应对它并为脚本用户提供描述性错误。

一个例子:

--  drop table thetransformersmorethanmeetstheeye
--  select * from thetransformersmorethanmeetstheeye



--  first batch begins here         

    begin tran

    create table thetransformersmorethanmeetstheeye(i int); -- non-erring if not yet existing

    -- even there's an error here, @@ERROR will be 0 on next batch
    ALTER TABLE [dbo].[Table1]  WITH CHECK ADD  CONSTRAINT [FK_constraint] FOREIGN KEY([field1], [field2])
    REFERENCES [dbo].[Table2] ([field3], [field4]);             

go  -- first batch ends here



--  second batch begins here

    if @@TRANCOUNT > 0 begin        
        PRINT 'I have a control here if things needed be committed or rolled back';

        -- @@ERROR is always zero here, even there's an error before the GO batch. 
        -- @@ERROR cannot span two batches, it's always gets reset to zero on next batch
        PRINT @@ERROR; 


        -- But you can choose whether to COMMIT or ROLLBACK non-erring things here
        -- COMMIT TRAN;
        -- ROLLBACK TRAN;

    end
    else if @@TRANCOUNT = 0 begin
        PRINT 'Sql Server automatically rollback the transaction. Nothing can do about it';
    end
    else begin
        PRINT 'Anomaly occured, @@TRANCOUNT cannot be -1, report this to Microsoft!';
    end

--  second batch implicitly ends here   

【讨论】:

    【解决方案2】:

    发生这种情况的唯一方法是在该 SPID 中没有打开的事务。

    就是这样。没有公开交易的唯一方法是:

    • 在旧事务提交或回滚后,您从未启动过新事务
    • 您在没有注意到的地方有另一个提交或回滚
    • 某些东西终止了您的连接或从您的 spid 外部强制回滚(例如来自另一个会话的 kill 命令)

    您没有提供太多代码。您的查询中是否有任何错误捕获或任何其他未显示的条件逻辑?

    【讨论】:

    • ans to 1:开始事务、回滚事务、提交事务是脚本中仅有的 3 个。 Ans to 2:不可能,搜索整个脚本,没有那种。回答3:这甚至可能吗?我在管理工作室窗口中手动运行脚本。没有错误捕获也没有条件逻辑,它是一个充满创建表、创建视图、更改表语句的脚本 1000+ 行,所以我不能在这里全部显示。
    • 1 - 你能复制它吗?您可以做的其他测试是添加 SELECT @@TRANCOUNT 以获取该 spid 中打开事务的当前计数。 2 - 如果您搜索,请确保只检查 commitrollbacktran 部分是可选的。 3 - 这是可能的。您的服务器上没有其他用户吗?
    • 1 - 是的可重现,请参阅我的问题中的编辑。 2 - 没有其他提交和回滚 3 - 我在本地服务器上创建了数据库,只有我登录。
    【解决方案3】:

    据我所知,ALTER TABLE 命令会创建自己的新事务,当它失败时,会回滚该事务。 proc 中的单个回滚将导致该 proc 中所有打开的事务被回滚。因此,您会看到错误,因为 ALTER TABLE 语句的失败会在您尝试执行之前隐式回滚您的事务..

    您可以通过检查代码中的@TRANCOUNT 很容易地确认这一点,并且只有在它不为零时才调用回滚

    【讨论】:

    • 我撒谎了!它不起作用。您可以通过以下方式进行测试:begin tran; ALTER TABLE blah add blah int; SELECT 'blah'; rollback tran
    • 它永远不会回滚,但它确实留下了一个打开的事务。
    • @JNK:它不会留下未结交易。如果 blah 列已经存在于 blah 表中并且您这样做:begin tran; ALTER TABLE blah add blah int; SELECT 'blah'; rollback tran,它会自动回滚,尽管不是通过您的 rollback tran 代码,它会由 Sql Server 自动回滚,它不会留下一个打开的事务。为了证明它被 Sql Server 自动回滚(因此没有打开事务),尝试打开一个 New Query 选项卡,并尝试在表上添加一个新列(尚不存在),它将被添加跨度>
    • 另一个模拟,留下一个打开的事务:BEGIN TRAN; ALTER TABLE blah add notyetexistingcolumn int;,它试图添加一个表上不存在的新列,只执行这两行(没有 COMMIT,没有 ROLLBACK),然后打开一个新的查询选项卡,然后还添加一个新的不存在的列ALTER TABLE blah add anothernotexistingcolumn int;,执行将停止,因为第一个查询选项卡上仍有一个挂起(左打开)的事务
    【解决方案4】:

    ALTER TABLE 语句中的错误是编译错误而不是运行时错误 - 因此该语句出现的整个批处理永远不会执行。我猜在BEGIN TRANSACTIONALTER TABLE 之间没有GO - 因此BEGIN TRANSACTION 从未执行过,而SQL Server 告诉你的是完全正确的。

    尝试在BEGIN TRANSACTION 之后立即添加GO

    【讨论】:

    • 我在我的回答中进一步提供了一个关于 Sql Server 和 Postgresql 的示例,以说明当您的 CATCH 部分中是否可以捕获错误时,Sql Server 有点不一致。在 Postgresql 中,所有 DDL 执行异常都是可捕获的。在 Sql Server 中,alter table z add zzz int; 之类的语句无法被您的代码捕获,而 alter table z drop column aaa; 则可以被您的代码捕获;他们之间的最大区别是什么?为了将 Sql Server 与其他技术(例如 Postgresql)进行对比,您的代码都可以捕获两个语句异常。
    【解决方案5】:

    鉴于此:

    create table z
    (
    i int identity(1,1) not null,
    zzz int not null
    );
    

    当您尝试以下操作时..

    begin try
    
        begin transaction
    
        alter table z drop column aaa;
    
        commit tran;
    
    end try
    begin catch 
    
        print 'hello';
        SELECT
            ERROR_NUMBER() as ErrorNumber,
            ERROR_MESSAGE() as ErrorMessage;
    
        IF (XACT_STATE()) = -1
        BEGIN
            PRINT
                N'The transaction is in an uncommittable state. ' +
                'Rolling back transaction.'
            ROLLBACK TRANSACTION;
        END;
    
    end catch
    
    print 'reached';
    

    ..错误可以被捕获:

    ErrorNumber ErrorMessage
    4924    ALTER TABLE DROP COLUMN failed because column 'aaa' does not exist in table 'z'.
    

    但是尝试将alter table z drop column aaa; 更改为alter table z add zzz int;,Sql Server 可以捕获错误..

    每个表中的列名必须是唯一的。表中的列名“zzz” 'z' 被多次指定。

    ..但不会将控制权交还给您,不会触发 CATCH 块。似乎没有严格的规则,哪些错误是可以捕获的,哪些不是。

    为了说明差异,这里是您的代码可捕获的错误

    这是您的代码无法捕获的错误,它与您的问题相似

    请注意那里没有网格(通过SELECT ERROR_NUMBER() as ErrorNumber, ERROR_MESSAGE() as ErrorMessage;)。这意味着,Sql Server 在检测到异常后并没有将控制权交还给您。

    也许您可以在此处查看其他可能有帮助的详细信息:http://msdn.microsoft.com/en-us/library/ms179296.aspx

    请参阅此指南以了解错误处理@@ERROR and/or TRY - CATCH


    顺便说一句,在 Postgresql 上,所有类型的 DDL 错误都可以被您的代码捕获。

    do $$
    
    
    begin
    
        -- alter table z drop column aaa;
        alter table z add zzz int;
    
    
    exception when others then 
    
        raise notice 'The transaction is in an uncommittable state. '
                         'Transaction was rolled back';
    
        raise notice 'Yo this is good! --> % %', SQLERRM, SQLSTATE;
    end;
    
    
    $$ language 'plpgsql';
    

    这是 Postgresql 上 alter table z drop column aaa; 的开发人员渲染错误消息:

    这是 Postgresql 上 alter table z add zzz int; 的开发人员渲染错误消息;顺便说一句,在 Sql Server 中,当这种类型的语句出现错误时,它不会将控制权交还给您,因此您的 CATCH 部分有时有用,有时无用。

    【讨论】:

    • 规则是你不能在同一级别的错误处理中捕获编译错误(很明显 - 如果它不能编译批处理,它就无法找到错误处理)
    • 我的答案没有编译错误,请尝试检查我的答案。底线是,您无法推断 Sql Server 中的给定 DDL 是否可以捕获。
    • 我尝试了您的代码,在显示错误后(未触发 catch 块),我能够回滚。无论如何,我没有在我的代码中使用 try...catch...。我的问题是我无法回滚我的交易。我不是想发现错误。
    • 是的,我知道您没有发现错误。然而,捕捉错误可以让您了解 Sql Server 的内部工作。您的代码有错误可能会被 Sql Server 视为严重错误,因此它会代表您执行回滚,这就是我可以得出的结论;因此,您无需回滚
    • re: 我试过你的代码,在显示错误后(没有触发catch块),我能够回滚。你可能模拟错误我的代码示例。你确定你可以回滚吗?在这里,我无法回滚它,当我尝试手动发出回滚时,结果是:ROLLBACK TRANSACTION 请求没有对应的BEGIN TRANSACTION。。我试图模拟和你的一样,我突出显示 BEGIN TRAN 和 ALTER TABLE,没有 COMMIT TRAN,然后执行;然后我尝试手动回滚,Sql Server说没有没有对应的BEGIN TRANSACTION
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-29
    • 1970-01-01
    • 1970-01-01
    • 2018-09-14
    相关资源
    最近更新 更多