[SQL Server]嵌套事务与分布式事务

 Sql Server支持嵌套事务:也就是说在前一事务未完成之前可启动一个新的事务,只有在外层的Commit Tran语句才会导致数据库的永久更改。
请尝试执行以下语句:

BEGIN  TRAN  tr0
       BEGIN TRAN tr1
       ROLLBACK TRAN tr1 
ROLLBACK TRAN tr0

执行结果:服务器: 消息 3903,级别 16,状态 1,行 5
ROLLBACK TRANSACTION 请求没有对应的 BEGIN TRANSACTION。

原因分析:
1)    Sql Server把每个连接开启的事务数目记录在全局变量@@trancount中,就象计数器一样,每个Begin Tran语句会让@@trancount自增1,每个Commit Tran语句会让@@trancount自减1,只有最外层的Commit Tran(当@@trancount=1)会将更改影响到数据库中,而不再存储在事务日志中。

2)   Sql Server只会记录外层事务名称如果企图回滚任一内层事务,错误就会出现。
3)    非常遗憾的是,不管嵌套的事务层次有多深,不带保存点的Rollback Tran语句将直接把@@trancount设置为0

解决思路:
1)   采用保存点:Sql Server提供了一种用于回滚部分事务的机制:Save Tran ,它不会对@@trancount产生任何影响,只是标记回滚事务时可以到达的点。

(但是这种方法不适用于“分布式事务”的远程调用,因为分布式事务不支持事务保存点: save transaction )
--定义一个是否为嵌套事务的标志
DECLARE  @nestedFlag BIT
IF(@@trancount>0)
BEGIN
    --是嵌套事务:使用保存点,避免再次嵌套
    SET  @nestedFlag=1
    SAVE TRAN TestA
END
ELSE
BEGIN
    --不是嵌套事务:开启一个事务
    SET  @nestedFlag=0
    BEGIN TRAN TestA
END
--执行业务操作,如果出错就回滚事务点,并立即返回
IF(@@error<>0)
BEGIN
    ROLLBACK TRAN TestA
    RETURN 0
END
--如果不是嵌套事务才提交
IF(@nestedFlag=0)
BEGIN
    COMMIT TRAN TestA
END

 

2)  如果存储过程 可能用于分布式事务,先判断是否被外层事务包括,如果是, 建议不管是否出错都commit, 然后用RAISERROR抛出一个异常,并返回一个错误码,由外部事务去判断是否要回滚。


--可在分布式事务中调用的存储过程样例.  

create procedure p_trans_test
as
begin
   declare @trancount int;
   set @trancount = @@trancount;

   begin transaction test1
  
   begin try
      print 1-- 业务逻辑
   end try
   begin catch

      if (@trancount = 0)
      begin
         rollback transaction test1;
         return -1
      end
      else
      begin
         commit transaction test1;
         -- 抛出异常与错误码,由外部调用者去处理是否回滚.
         RAISERROR ('Error raised in TRY block.', -- Message text.
               16, -- Severity.
               1 -- State.
               );
         return -1;
      end
   end catch
   return 0;
end

你可能感兴趣的:(sql,数据库,server,服务器,存储)