代码之家  ›  专栏  ›  技术社区  ›  MatBailie

仅提交可能回滚的事务内部所做的特定更改

  •  5
  • MatBailie  · 技术社区  · 16 年前

    这是对原始问题的重要编辑,使其更加简洁,涵盖了现有答案提出的要点。。。

    是否可以在一个事务中对多个表进行多次更改,并仅回滚部分更改?

    在下面的TSQL中,我不希望“myLogSP”所做的任何更改被回滚。但各种MyBusinessSP所做的所有更改都应该在必要时回滚。

    BEGIN TRANSACTION  
    
        EXEC myLogSP
    
        EXEC @err = myBusinessSPa
        IF (@err <> 0) BEGIN ROLLBACK TRANSACTION RETURN -1 END
    
        EXEC myLogSP
    
        EXEC @err = myBusinessSPb
        IF (@err <> 0) BEGIN ROLLBACK TRANSACTION RETURN -1 END
    
        EXEC myLogSP
    
        EXEC @err = myBusinessSPc
        IF (@err <> 0) BEGIN ROLLBACK TRANSACTION RETURN -1 END
    
        EXEC myLogSP
    
    COMMIT TRANSACTION
    RETURN 0
    

    顺序很重要,myLogSPs必须在myBusinessSPs之间和之后进行(myLogSPs接收myBusinessSPs所做的更改)

    同样重要的是,所有MyBusinessSP都发生在一个事务中,以保持数据库的完整性,并在必要时允许其所有更改回滚。

    就好像我想让MyLogSP表现得好像它们不是事务的一部分一样。这只是一个不方便的事实,它们恰好位于一个内部(因为需要在MyBusinessSP之间调用)

    最后的答案是“不”,唯一的选择是重新设计代码。要么使用表变量进行日志记录(因为变量不会回滚),要么重新设计业务逻辑以不需要事务。。。

    7 回复  |  直到 16 年前
        1
  •  5
  •   vladr    16 年前

    使用 SAVEPOINT s ,例如。

    BEGIN TRANSACTION  
    
        EXEC myLogSP
    
        SAVE TRANSACTION savepointA
        EXEC @err = myBusinessSPa
        IF (@err <> 0) BEGIN
            ROLLBACK TRANSACTION savepointA
            COMMIT
            RETURN -1
        END
    
        EXEC myLogSP
    
        SAVE TRANSACTION savepointB
        EXEC @err = myBusinessSPb
        IF (@err <> 0) BEGIN
            ROLLBACK TRANSACTION savepointB
            COMMIT
            RETURN -1
        END
    
        EXEC myLogSP
    
        SAVE TRANSACTION savepointC
        EXEC @err = myBusinessSPc
        IF (@err <> 0) BEGIN
            ROLLBACK TRANSACTION savepointC
            COMMIT
            RETURN -1
        END
    
        EXEC myLogSP
    
    COMMIT TRANSACTION
    

    根据目前提供的信息(以及我对它的理解),您似乎必须重新设计您的日志SP,以使用变量或使用文件,或允许它们“事后”运行,如下所示:

    BEGIN TRANSACTION  
    
        SAVE TRANSACTION savepointA
        EXEC @err = myBusinessSPa
        IF (@err <> 0) BEGIN
            ROLLBACK TRANSACTION savepointA
            EXEC myLogSPA -- the call to myBusinessSPa was attempted/failed
            COMMIT
            RETURN -1
        END
    
        SAVE TRANSACTION savepointB
        EXEC @err = myBusinessSPb
        IF (@err <> 0) BEGIN
            ROLLBACK TRANSACTION savepointB
            EXEC myLogSPA -- the call to myBusinessSPa originally succeeded
            EXEC myLogSPB -- the call to myBusinessSPb was attempted/failed
            COMMIT
            RETURN -1
        END
    
        SAVE TRANSACTION savepointC
        EXEC @err = myBusinessSPc
        IF (@err <> 0) BEGIN
            ROLLBACK TRANSACTION savepointC
            EXEC myLogSPA -- the call to myBusinessSPa originally succeeded
            EXEC myLogSPB -- the call to myBusinessSPb originally succeeded
            EXEC myLogSPC -- the call to myBusinessSPc was attempted/failed
            COMMIT
            RETURN -1
        END
    
        EXEC myLogSPA -- the call to myBusinessSPa succeeded
        EXEC myLogSPB -- the call to myBusinessSPb succeeded
        EXEC myLogSPC -- the call to myBusinessSPc succeeded
    
    COMMIT TRANSACTION
    
        2
  •  2
  •   Tom H zenazn    16 年前

    您需要基本上跳出当前上下文。有几种方法可以做到这一点。其中一个(我从未尝试过)是调用CLR来执行插入。

    CREATE TABLE dbo.Test_Transactions
    (
         my_string VARCHAR(20) NOT NULL
    )
    GO
    
    DECLARE
         @tbl TABLE (my_string VARCHAR(20) NOT NULL)
    
    BEGIN TRANSACTION
    
    INSERT INTO dbo.Test_Transactions (my_string) VALUES ('test point one')
    
    INSERT INTO @tbl (my_string) VALUES ('test point two')
    
    INSERT INTO dbo.Test_Transactions (my_string) VALUES ('test point three')
    
    ROLLBACK TRANSACTION
    
    INSERT INTO dbo.Test_Transactions (my_string) select my_string from @tbl
    
    SELECT * FROM dbo.Test_Transactions
    SELECT * FROM @tbl
    GO
    
        3
  •  2
  •   HLGEM    16 年前

    我们很幸运地将日志条目放入表变量中,然后在提交或回滚之后插入到实际表中。

    create table #templog (fie1d1 int, field2 varchar(10))
    
    declare @templog table (fie1d1 int, field2 varchar(10))
    
    BEGIN TRANSACTION      
    insert into #templog
    Exec my_proc
    
    insert into @templog (fie1d1, field2)
    select t.* from #templog t 
    left join @templog t2 on t.fie1d1 = t2.fie1d1 where t2.fie1d1 is null
    
    insert into templog
    values (1, 'test')
    
    rollback tran
    select * from #templog
    select * from templog
    select * from @templog
    
        4
  •  1
  •   rick schott    16 年前
        5
  •  0
  •   Moose    16 年前

    我真的没有一个关于桌子锁的答案给你,我想你已经有了答案,会有的 由于标识列可能会回滚,因此无法设置为表锁。

        6
  •  0
  •   CodeMonkey1313    16 年前

        7
  •  0
  •   Daniel    16 年前

    也许您可以将业务表的插入/更新放在它们自己的原子事务t1中,并将这些事务中的每个事务包装在另一个事务t2中,该事务t2执行日志表更新和t1(业务表更新),而不进行任何回滚。例如:

    BEGIN TRANSACTION t2
         <insert to log>
         <execute stored procedure p1>
    END TRANSACTION t2
    
    CREATE PROCEDURE p1
    AS
         BEGIN TRANSACTION t1
             <insert to business tables>
             <rollback t1 on error>
         END TRANSACTION t1
    

    我相信,当您在存储过程中回滚t1时,调用事务t2将不受影响。