关于存储过程与事物

事务

事务组合一系列任务为一个执行单元。每个事务以特定的任务开始,以特定的任务结束。当所有的任务成功时事务成功,当任何一个任务失败时,事务失败。所以一个事务只有两个结果:失败或成功。
用户能用下列指令组合两个以上的T-SQL语句到一个事务中:
Begin Transaction
Rollback Transaction
Commit Transaction

一组指令中出现任何错误,就需要使用Rollback撤销执行。如果一个事务中的所有语句都按顺序执行成功,那么就需要把所有的改变记录到数据库中:Commit到数据库。

  1. USE pubs   
  2.   
  3. DECLARE @intErrorCode INT  
  4.   
  5. BEGIN TRAN   
  6.     UPDATE Authors   
  7.     SET Phone = '415 354-9866'  
  8.     WHERE au_id = '724-80-9391'  
  9.   
  10.     SELECT @intErrorCode = @@ERROR   
  11.     IF (@intErrorCode <> 0) GOTO PROBLEM   
  12.   
  13.     UPDATE Publishers   
  14.     SET city = 'Calcutta', country = 'India'  
  15.     WHERE pub_id = '9999'  
  16.   
  17.     SELECT @intErrorCode = @@ERROR   
  18.     IF (@intErrorCode <> 0) GOTO PROBLEM   
  19. COMMIT TRAN   
  20.   
  21. PROBLEM:   
  22. IF (@intErrorCode <> 0) BEGIN  
  23. PRINT 'Unexpected error occurred!'  
  24.     ROLLBACK TRAN   
  25. END  

在真正处理开始之前,使用BEGIN TRAN标记:下面所有的动作作为一个事务。里边包含两个update语句。如果没有出错,当执行到COMMIT TRAN所有的改变被提交到数据库,存储过程完成。如果在执行update出错,通过检测@@ERROR,跳转到PROBLEM标签,显示错误信息,回滚这期间所有的修改。
注意:确保BEGIN TRAN 和COMMIT 或 ROLLBACK成对出现。

嵌套事务

SQL Server 容许你嵌套事务,这个特性意味着,即使上一个事务没有完成,也能开启一个新事务。T-SQL容许你通过嵌套BEGIN TRAN嵌套事务。 内置变量@@TRANCOUNT可显示事务的嵌套层级。0表示没有嵌套,1表示嵌套一层,依此类推。

COMMIT提交本层事务,但是直到最外层的事务提交了,所有的改变才保存到磁盘。它仅仅时减小@@TRANCOUNT的值。ROLLBACK不论在那层都能回滚所有的事务。
当你开始一个事务,@@TRANCOUNT变量自动从0增加到1;当提交了数量减少1个。当回滚了,则数量减为0。正如你所见:COMMIT和ROLLBACK并不对称。如果你嵌套事务,COMMIT一层较少1,如图1。ROLLBACK命令回滚所有事务如图2。COMMIT 和 ROLLBACK不同在于嵌套错误处理。

 图1:一个commit对应一个BeginTransaction,把@@Trancount数量减1。

图2:Rollback一直回滚整个事务

从图1和图2可以看出:你可以嵌套事务,使用@@TRANCOUNT检测嵌套的级别。也能了解COMMIT 和ROLLBACK不对称的特性。COMMIT减少@@TRANCOUNT的值, ROLLBACK让值变为0。只有最后一个COMMIT才真正提交事务。不管你嵌套多少事务,只有最后一个COMMIT起作用。

  1. USE pubs   
  2. SELECT 'Before BEGIN TRAN', @@TRANCOUNT  --  @@TRANCOUNT :0   
  3.   
  4. BEGIN TRAN   
  5.     SELECT 'After BEGIN TRAN', @@TRANCOUNT  -- @@TRANCOUNT :1   
  6.   
  7.     DELETE sales   
  8.     BEGIN TRAN nested   
  9.         SELECT 'After BEGIN TRAN nested', @@TRANCOUNT   
  10.                    -- @@TRANCOUNT :2   
  11.   
  12.         DELETE titleauthor   
  13.     COMMIT TRAN nested   
  14.                    -- 出了减少@@TRANCOUNT的值,不做任何事情
  15.   
  16.   
  17.     SELECT 'After COMMIT TRAN nested', @@TRANCOUNT   
  18.                    -- @@TRANCOUNT :1   
  19.   
  20. ROLLBACK TRAN   
  21.   
  22. SELECT 'After ROLLBACK TRAN', @@TRANCOUNT  -- @@TRANCOUNT :0    
  23.   
  24. -- 因为ROLLBACK TRAN 回滚整个事务。 @@TRANCOUNT 被设置为 0.   
  25.   
  26.   
  27. SELECT TOP 5 au_id FROM titleauthor   
  1. USE pubs   
  2. SELECT 'Before BEGIN TRAN', @@TRANCOUNT  -- @@TRANCOUNT :0   
  3.   
  4. BEGIN TRAN   
  5.     SELECT 'After BEGIN TRAN', @@TRANCOUNT  -- @@TRANCOUNT :1   
  6.   
  7.     DELETE sales   
  8.     BEGIN TRAN nested   
  9.         SELECT 'After BEGIN TRAN nested', @@TRANCOUNT   
  10.                -- @@TRANCOUNT :2   
  11.   
  12.         DELETE titleauthor   
  13.     ROLLBACK TRAN   
  14.      
  15.     SELECT 'After COMMIT TRAN nested', @@TRANCOUNT   
  16.     -- @@TRANCOUNT :0 因为ROLLBACK TRAN 回滚整个事务。 @@TRANCOUNT 被设置为 0.  
  17.   
  18.   
  19. IF (@@TRANCOUNT > 0) BEGIN  
  20.     COMMIT TRAN -- 永远不会执行到这里,因为事务被回滚了。  
  21.   
  22.     SELECT 'After COMMIT TRAN', @@TRANCOUNT   
  23. END  
  24.   
  25. SELECT TOP 5 au_id FROM titleauthor   


在这个例子中,永远不会执行到COMMIT TRAN,因为ROLLBACK TRAN回滚了所有的事务。除非ROLLBACK TRAN带有一个保存点调用,否则ROLLBACK TRAN一直回滚所有事务,把@@TRANCOUNT设置为0。

SAVE TRAN 和保存点

保存点提供了事务部分回滚的机制。用户能在事务内部设置保存点或标记。保存点定义一个当事务被撤销时,回滚的位置。SQL Server容许用

户通过SAVE TRAN语句定义保存点。申明保存点不影响@@TRANCOUNT的值,回滚到保存点也不影响@@TRANCOUNT。回滚必须ROLLBACK TRAN带有保存点的名称,否则回滚整个事务。

  1. USE pubs   
  2. SELECT 'Before BEGIN TRAN main', @@TRANCOUNT   
  3.    -- @@TRANCOUNT :0   
  4.   
  5.   
  6. BEGIN TRAN main   
  7.     SELECT 'After BEGIN TRAN main', @@TRANCOUNT   
  8.    -- @@TRANCOUNT :1   
  9.   
  10.     DELETE sales   
  11.     SAVE TRAN sales  -- 设置一个保存点  
  12.   
  13.     SELECT 'After SAVE TRAN sales', @@TRANCOUNT   
  14.    -- @@TRANCOUNT 值仍然是 1   
  15.   
  16.   
  17.     BEGIN TRAN nested   
  18.         SELECT 'After BEGIN TRAN nested', @@TRANCOUNT   
  19.         -- @@TRANCOUNT :2   
  20.   
  21.         DELETE titleauthor   
  22.         SAVE TRAN titleauthor  -- Mark a save point   
  23.   
  24.         SELECT 'After SAVE TRAN titleauthor', @@TRANCOUNT   
  25.         -- @@TRANCOUNT 仍然是 2   
  26.   
  27.     ROLLBACK TRAN sales   
  28.   
  29.     SELECT 'After ROLLBACK TRAN sales', @@TRANCOUNT   
  30.      -- @@TRANCOUNT 仍然是 2   
  31.   
  32.   
  33.     SELECT TOP 5 au_id FROM titleauthor   
  34.   
  35. IF (@@TRANCOUNT > 0) BEGIN  
  36.     ROLLBACK TRAN   
  37.     SELECT 'AFTER ROLLBACK TRAN', @@TRANCOUNT   
  38.     -- @@TRANCOUNT :0 因为ROLLBACK TRAN 回滚整个事务。 @@TRANCOUNT 被设置为 0.  
  39.   
  40. END  
  41.        
  42. SELECT TOP 5 au_id FROM titleauthor   

错误处理

下面的例子是一个和数据库交互的很理想化的存储过程。当一个错误出现时,你所能做的是:终止代码执行顺序。要么把代码路由到别的代码

,或者返回终止程序。@@ERROR这个系统变量常用来实现错误处理。它包含SQL语句最后执行错误的代码。当代码执行成功, @@ERROR的值是0
当代码执行完成,立即使用IF语句判断@@ERROR的值。这是因为当下一个语句执行成功时,@@ERROR的值会被重置为0。所以必须立即捕获。经常在执行完NSERT, UPDATE, 或 DELETE语句后,你需要立即测试@@ERROR值的变化。

这种解决方案有很多重复的处理,特别是如果你的业务逻辑需要2个以上的T-SQL语句时。比较优雅的解决方案是组合所有的代码,使用一个通用的错误处理。

  1. CREATE PROCEDURE addTitle(@title_id VARCHAR(6), @au_id VARCHAR(11),    
  2.                           @title VARCHAR(20), @title_type CHAR(12))   
  3. AS  
  4.   
  5. BEGIN TRAN   
  6.     INSERT titles(title_id, title, type)   
  7.     VALUES (@title_id, @title, @title_type)   
  8.   
  9.     IF (@@ERROR <> 0) BEGIN  
  10.         PRINT 'Unexpected error occurred!'  
  11.         ROLLBACK TRAN   
  12.         RETURN 1   
  13.     END  
  14.   
  15.     INSERT titleauthor(au_id, title_id)   
  16.     VALUES (@au_id, @title_id)   
  17.   
  18.     IF (@@ERROR <> 0) BEGIN  
  19.         PRINT 'Unexpected error occurred!'  
  20.         ROLLBACK TRAN   
  21.         RETURN 1   
  22.     END  
  23.   
  24. COMMIT TRAN   
  25.   
  26. RETURN 0   

关于多个存储过程和事物的处理

 

问题有点复杂。
假如我有叁个存储过程,每个存储过程中都显示申明了事物,当我依次调用这叁个存储过程,如果其中有一个存储过程中发生了错误,那么其它两个存储过程中的事物都要回滚,这样的如何做呢?
下面我把代码贴出来让大家看看。
SQL code
    
    
-- --------------第一个存储过程----------------- CREATE PROCEDURE TransProc_1 @PriKey INT , @CharCol CHAR ( 3 ), @error_out int output AS BEGIN TRANSACTION InProc_1 INSERT INTO TestTrans VALUES ( @PriKey , @CharCol ) INSERT INTO TestTrans VALUES ( @PriKey + 1 , @CharCol ) set @error_out = @@error COMMIT TRANSACTION InProc_1; GO -- ------------第二个存储过程------------------- CREATE PROCEDURE TransProc_2 @PriKey INT , @CharCol CHAR ( 3 ), @error_out int output AS BEGIN TRANSACTION InProc INSERT INTO TestTrans VALUES ( @PriKey , @CharCol ) INSERT INTO TestTrans VALUES ( @PriKey + 1 , @CharCol ) set @error_out = @@error COMMIT TRANSACTION InProc; GO -- ------------第叁个存储过程------------------- CREATE PROCEDURE TransProc_3 @PriKey INT , @CharCol CHAR ( 3 ), @error_out int output AS BEGIN TRANSACTION InProc INSERT INTO TestTrans VALUES ( @PriKey , @CharCol ) INSERT INTO TestTrans VALUES ( @PriKey + 1 , @CharCol ) set @error_out = @@error COMMIT TRANSACTION InProc; GO -- ------------------------------- BEGIN TRANSACTION OutOfProc; DECLARE @ERROR INT ; -- 依次调用这些存储过程 EXEC TransProc_1 10 , ' aaa ' , @error_out = @ERROR output EXEC TransProc_2 20 , ' ccc ' , @error_out = @ERROR output EXEC TransProc_3 30 , ' ccc ' , @error_out = @ERROR output -- 打印错误号 print ( @ERROR ) IF ( @ERROR <= 0 ) BEGIN PRINT ( ' 提交事物 ' ) COMMIT TRANSACTION OutOfProc; END ELSE BEGIN PRINT ( ' 事物回滚 ' ) ROLLBACK TRANSACTION OutOfProc; END GO
上面的代码运行肯定是不行的,不能达到我想要的效果,请问有什么方法可以解决我现在的问题呢?
============================================================================
问题答案:
BEGIN TRANSACTION OutOfProc;
DECLARE @ERROR INT,@ERROR_2 INT;
set @ERROR_2=0
set @ERROR=0
--依次调用这些存储过程
EXEC TransProc_1 12, 'aaa',@error_out=@ERROR output
set @ERROR_2=@ERROR_2+@ERROR
EXEC TransProc_2 22, 'bbb',@error_out=@ERROR output
set @ERROR_2=@ERROR_2+@ERROR
EXEC TransProc_3 30, 'ccc',@error_out=@ERROR output
set @ERROR_2=@ERROR_2+@ERROR
--打印错误号
print(@ERROR_2)
IF ( @ERROR_2 <= 0 ) BEGIN
    PRINT('提交事物')
    COMMIT TRANSACTION OutOfProc;
END ELSE BEGIN
    PRINT('事物回滚')
    ROLLBACK TRANSACTION OutOfProc;
END
GO
至于错误处理, 那是你在每个可能出错的语句后需要添加代码的(sql 2005 可以直接用try ... catch) 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值