SQL Server事务性编程总结

目录

为了保证数据的一致性,事务在数据库编程中经常用到,这里对T-SQL中事务的写法做一下总结与解读。

T-SQL中三种事务写法

  • SQL Sever 2000时代的写法,每条语句后都加@@Error判断
/* 如果只在最后一句加上IF @@ERROR <> 0 的判断,是不理想的,因为@@ERROR只对最后执行的那条SQL有效,
这样中间出现的错误就判断不了 */
BEGIN TRAN;
	INSERT INTO dual VALUES(-1); 
	IF @@ERROR <> 0 
	GOTO ErrM;
	
	-- 除数为0,出现运行时错误
	INSERT INTO dual VALUES (1/0);
	IF @@ERROR <> 0 
	GOTO ErrM;
	
	INSERT INTO dual VALUES (1/1);
	IF @@ERROR <> 0
	GOTO ErrM;
	    
COMMIT TRAN;
ErrM:
	ROLLBACK TRAN;
  • SQL Server 2005新增写法之一,开头加上 Set xAct_Abort On
SET XACT_ABORT ON;
BEGIN TRAN;
    INSERT INTO dual VALUES(-1); 
	INSERT INTO dual VALUES (1/0);
	INSERT INTO dual VALUES (1/1);
COMMIT TRAN;
  • SQL Server 2005新增写法之二,Try..Catch
BEGIN TRY
	BEGIN TRAN;
		INSERT INTO dual VALUES(-1); 
		INSERT INTO dual VALUES (1/0);
		INSERT INTO dual VALUES (1/1);
	COMMIT TRAN;
END TRY
BEGIN CATCH
	ROLLBACK TRAN;
END CATCH

关于Try..Catch

  • Try..Catch并不能捕获所有异常
    例如以下两种异常,Try..Catch并不能捕获:
    • 编译错误,如语法错误等
    • 语句级重新编译,如存储过程中某个表名不存在等

例如,如下存储过程,引用不存在的表:

Create PROC sp_MyTest AS
BEGIN TRY
	BEGIN TRAN;
		INSERT INTO TableExist VALUES(1);
		INSERT INTO TableNotExist VALUES (2);
	COMMIT TRAN;
END TRY
BEGIN CATCH
    SELECT 
        ERROR_NUMBER() AS ErrorNumber,
        ERROR_MESSAGE() AS ErrorMessage;
        ROLLBACK TRAN;
END CATCH;

以上SP在新建时没有问题,但在运行时就会报错:

Msg 208, Level 16, State 1, Procedure sp_MyTest, Line 6 [Batch Start Line 41]
Invalid object name 'TableNotExist'.
Msg 266, Level 16, State 2, Procedure sp_MyTest, Line 6 [Batch Start Line 41]
Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 0, current count = 1.

而且CATCH中内容并没有执行,所以==事务也没有回滚==。

  • 嵌套事务回滚的问题
    如下为嵌套事务示例代码,实际更多出现在主SP调用子SP,两个SP都有事务的情形下。
    结果出现了报错:The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION
BEGIN TRY
	BEGIN TRANSACTION;
		INSERT INTO dual VALUES(1);
			-- nested try catch & transaction begin
			BEGIN TRY
				BEGIN TRANSACTION;
					INSERT INTO dual VALUES(1/0);
				COMMIT TRANSACTION;
			END TRY
			BEGIN CATCH
				ROLLBACK TRANSACTION;
			END CATCH 
			-- nested try catch & transaction end
	COMMIT TRANSACTION;
END TRY
BEGIN CATCH		
	ROLLBACK TRANSACTION;
END CATCH

问题原因在于ROLLBACK默认是回滚所有事务,即直到最外面的事务。
可以通过在CATCH模块的RollBack前加一个IF @@TRANSCOUNT > 0解决,或者开始事务前,设一个 SAVE TRANSACTION

关于Set xAct_Abort On

这是从SQL Server 2005开始出现的一个选项,默认为OFF,遇到异常时,根据异常的种类,可能只会回滚引发异常的那条语句(比如主键冲突),而继续执行后续语句,并提交事务。若设为ON,则从异常语句开始回滚整个事务,不再继续执行后续语句(这点很重要,类似于shell脚本的set -e选择)。另外,如果错误非常严重,即便xAct_Abort选项为OFF,也可能会回滚整个事务。如果加上Set xAct_Abort on,即使try catch无法捕获异常,最终也会回滚事务。