5

这是对原始问题的重要修改,使其更加简洁并涵盖了现有答案提出的要点......

是否可以在单个事务中对多个表进行多次更改,并且只回滚一些更改?

在下面的 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 之间调用),这只是一个不便的事实。

编辑:

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

4

7 回答 7

5

使用SAVEPOINTs,例如

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
于 2009-03-06T14:00:00.653 回答
2

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

好的,如果您不在 SQL Server 2008 上,请尝试此方法。这很混乱,是一种解决方法,但它应该可以工作。#temp 表和表变量必须使用 sp 返回的结构进行设置。

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
于 2009-03-06T14:46:14.193 回答
2

您基本上需要跳出当前上下文。有几种方法可以做到这一点。一种(我从未尝试过)是调用 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
于 2009-03-06T14:46:58.263 回答
1

使用保存点事务隔离级别

于 2009-03-06T14:20:27.033 回答
0

将日志插入移到事务之外不是简单的方法吗?

对于表锁,我真的没有答案,我想你已经有了答案,必须有一个表锁,因为标识列可能会回滚。

于 2009-03-06T13:59:33.293 回答
0

将 BEGIN TRANSACTION 语句移到第一次插入之后。

于 2009-03-06T14:00:19.733 回答
0

也许您可以将业务表的插入/更新放在它们自己的原子事务 t1 中,并将这些事务中的每一个都包装在另一个事务 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 不受影响。

于 2009-03-06T15:17:38.503 回答