1、背景

Job:一个步骤执行两个存储过程ProcA、ProcB。ProcA定义一个游标,从表TabA中检索数据,逐条插入到表TabB。如果某条数据不满足TabB上的约束(比如非空)导致插入失败。那么游标马上结束,出错之前的保留,出错之后的不会插入到TabB。过程ProcB不会被执行,Job报错,终止。
如果将ProcA放到查询窗口执行,它会跳过出错的数据,继续执行之后的插入。
例如TabA有100条记录,其中第50条不满足TabB上的约束,那么在Job中,只有前49条插入到TabB;在查询窗口执行,会有99条数据插入到TabB。

上面这段话是十月份处理一个出错作业,结合之前似曾遇到类似问题记录下来的。当时想着有空的时候把相似的问题挖出来,再进行对比。先来看下这个出错作业,作业的逻辑很简单,insert into A select columnlist from B inner join C on B.userid=C.userid(这是我简化后的等效语句)。实际的语句却是B left join C定义为游标,然后一行行的insert into A。作业一直在报错,没有开发蹦出来说数据有问题,也没有运维处理数据库上的各种错误。甚至怀疑这些作业是否有存在的必要!
错误信息提示很明显,不能将空值插入到NOT NULL列。B left join C就有可能返回NULL值,最简单就改成inner join(实际业务需求也要求存在于两表中)。查看A表的数据,每天有2W+的记录,但B inner join C有4W+,于是下结论"对于游标出错,出错之前的将保留,出错后的不会记录。"当时鬼使神差的拿存储过程在查询窗口执行,也有报错,但在查看A表中的记录时,发现insert4W+的记录,只有C返回为NULL的记录没插入。多次对比作业和查询窗口,同样的存储过程,插入到A中的记录数就是不一样。于是就有了结论"查询窗口与Job对"出错"的处理并不是完全相同。"
上面的结论其实很误人,第一条以偏概全;第二条说得过于勉强,仅仅是从结果推测查询窗口和Job对错误的处理机制不同,却不知道哪里不同。这篇文章的补充得益于之前的两篇文章捕获Insert触发器失败记录insert into linksvr or insert into from linksvr,回过头阅读这两篇文章的时候发现都有提SET XACT_ABORT ON/OFF。
2、XACT_ABORT

当SET XACT_ABORT为ON时,如果Transact-SQL语句运行时产生错误,整个事务将终止并回滚。为OFF时,只回滚产生错误的Transact-SQL语句,而事务将继续进行处理。编译错误(如语法错误)不受SET XACT_ABORT 的影响。
对于大多数 OLE DB 提供程序(包括 SQL Server),隐性或显式事务中的数据修改语句必须将XACT_ABORT设置为ON。唯一不需要该选项的情况是提供程序支持嵌套事务时。有关更多信息,请参见分布式查询和分布式事务。
SET XACT_ABORT的设置是在执行或运行时设置,而不是在分析时设置。

在之前的两篇文章中仅限于使用,但没去深究XACT_ABORT的用途。下面参考示例并适当补充

USE Test
GO
CREATE TABLE t1 (a int PRIMARY KEY)
CREATE TABLE t2 (a int REFERENCES t1(a),remark VARCHAR(32),rundate datetime default(getdate()))
GO
INSERT INTO t1 VALUES (1)
INSERT INTO t1 VALUES (3)
INSERT INTO t1 VALUES (4)
INSERT INTO t1 VALUES (6)
INSERT INTO t1 VALUES (7)
INSERT INTO t1 VALUES (9)
INSERT INTO t1 VALUES (10)
INSERT INTO t1 VALUES (12)
GO
/*显式事务*/
--只回滚错误行,事务内的语句继续执行
SET XACT_ABORT OFF
GO
BEGIN TRAN
    INSERT INTO t2(a,remark) VALUES (1,'显式事务XACT_ABORT OFF')
    INSERT INTO t2(a,remark) VALUES (2,'显式事务XACT_ABORT OFF') /* Foreign key error */
    INSERT INTO t2(a,remark) VALUES (3,'显式事务XACT_ABORT OFF')
COMMIT TRAN
GO
--事务终止并全部回滚
SET XACT_ABORT ON
GO
BEGIN TRAN
    INSERT INTO t2(a,remark) VALUES (4,'显式事务XACT_ABORT ON')
    INSERT INTO t2(a,remark) VALUES (5,'显式事务XACT_ABORT ON') /* Foreign key error */
    INSERT INTO t2(a,remark) VALUES (6,'显式事务XACT_ABORT ON')
COMMIT TRAN
GO
/*隐性事务,是否加BEGIN..END效果一样*/
--只回滚错误行,错误行后的语句继续执行
SET XACT_ABORT OFF
GO
BEGIN
    INSERT INTO t2(a,remark) VALUES (7,'隐性事务XACT_ABORT OFF')
    INSERT INTO t2(a,remark) VALUES (8,'隐性事务XACT_ABORT OFF') /* Foreign key error */
    INSERT INTO t2(a,remark) VALUES (9,'隐性事务XACT_ABORT OFF')
END
GO
--回滚错误行,错误行之前的不回滚,错误行之后的不执行
SET XACT_ABORT ON
GO
BEGIN
    INSERT INTO t2(a,remark) VALUES (10,'隐性事务XACT_ABORT ON')
    INSERT INTO t2(a,remark) VALUES (11,'隐性事务XACT_ABORT ON') /* Foreign key error */
    INSERT INTO t2(a,remark) VALUES (12,'隐性事务XACT_ABORT ON')
END
GO
View Code

相关文章: