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
执行语句,消息显示"查询已完成,但有错误。"查看t2表中数据
开启显式事务:SET XACT_ABORT OFF,只回滚错误行,事务内的语句继续执行;SET XACT_ABORT ON,事务终止并全部回滚
隐性事务:SET XACT_ABORT OFF,只回滚错误行,错误行后的语句继续执行;SET XACT_ABORT ON,回滚错误行,错误行之前的不回滚,错误行之后的不执行
也就是XACT_ABORT为OFF,只回滚错误行,事务内/事务后的语句继续执行;为ON,回滚错误行的事务(隐性对应的就是错误行,显式对应的是错误行所在的整个事务),然后终止。
那默认查询窗口是哪种状态?打开一个新建查询,执行下面语句
INSERT INTO t2(a,remark) VALUES (10,'默认')
INSERT INTO t2(a,remark) VALUES (11,'默认') /* Foreign key error */
INSERT INTO t2(a,remark) VALUES (12,'默认')
a=10、12是默认情况下插入成功的,通过对比和SET XACT_ABORT OFF时一致
然后将下面语句放到作业下执行
INSERT INTO t2(a,remark) VALUES (10,'代理')
INSERT INTO t2(a,remark) VALUES (11,'代理') /* Foreign key error */
INSERT INTO t2(a,remark) VALUES (12,'代理')
怎么代理写了两条,这和查询窗口是一样的。。。我预想的是在Job中XACT_ABORT默认为ON,三条语句第一条插入进去,第二条失败,结束。问题解决,GAME OVER~为什么不按套路出牌?换成游标也是这样,哪里有问题?
declare @id int,@remark varchar(20)
declare loop_cursor cursor for
select id,remark from (
select 10 id,'代理' remark
union all
select 11 id,'代理' remark /* Foreign key error */
union all
select 12 id,'代理' remark
) a order by id
open loop_cursor
fetch next from loop_cursor into @id,@remark
while @@fetch_status = 0
begin
insert into t2(a,remark) values(@id,@remark)
fetch next from loop_cursor into @id,@remark
end
close loop_cursor
deallocate loop_cursor
3、回顾问题作业
3.1、原始存储过程
--原始存储过程
ALTER PROCEDURE [dbo].[DBA_TroubleShooting]
AS
BEGIN
Declare @UserID int,@Accounts varchar(31),@NickName varchar(50),@Score int,@Amount int,@Amount2 int,@Amount3 int
Declare loop_cursor CURSOR FOR
Select T.userID,ai.Accounts,ai.NickName,T.Amount,T.Amount2,T.Amount3,T.Score
from UserFieldsOfJinGui T with(nolock)
left join AccountsInfoSimple ai with(nolock) on T.userID=ai.userID
order by T.userID
Open loop_cursor
FETCH NEXT FROM loop_cursor INTO @userID,@Accounts,@NickName,@Amount,@Amount2,@Amount3,@Score
While @@FETCH_STATUS=0
Begin
insert into TortoiseInfo_dba(userID,Accounts,NickName,Amount,Amount2,Amount3,Score,statisticsDate,runDate)
values(@userID,@Accounts,@NickName,@Amount,@Amount2,@Amount3,@Score,Convert(varchar(10),dateadd(day,-1,getdate()),120),getDate())
FETCH NEXT FROM loop_cursor INTO @userID,@Accounts,@NickName,@Amount,@Amount2,@Amount3,@Score
End
Close loop_cursor
Deallocate loop_cursor
END
新建查询,结合XACT_ABORT,查看存储过程执行结果
/****原始存储过程****/
--查询窗口
EXEC DBA_TroubleShooting --写入42757行,所有非空的数据都可以插入
--查询窗口+SET XACT_ABORT ON
SET XACT_ABORT ON --放在存储过程前面,或封装到存储过程里面效果相同
EXEC DBA_TroubleShooting
SET XACT_ABORT OFF
--写入25032行,XACT_ABORT为ON,回滚错误行的事务(隐性对应的就是错误行,显式对应的是错误行所在的整个事务),然后终止。
新建作业,分别将查询窗口中的语句放到作业步骤
--作业
已以用户 ** 的身份执行。 不能将值 NULL 插入列 'Accounts',表 'LK78DB.dbo.TortoiseInfo_dba';列不允许有空值。INSERT 失败。 [SQLSTATE 23000] (错误 515) 语句已终止。 [SQLSTATE 01000] (错误 3621). 该步骤失败。
写入25044行,只有部分非空的数据可以插入
--作业+SET XACT_ABORT ON
已以用户 ** 的身份执行。 不能将值 NULL 插入列 'Accounts',表 'LK78DB.dbo.TortoiseInfo_dba';列不允许有空值。INSERT 失败。 [SQLSTATE 23000] (错误 515). 该步骤失败。
写入25032行,与查询窗口XACT_ABORT为ON时相同
left join中ai.Accounts有部分为空,不管是查询窗口还是作业,XACT_ABORT的设置会影响到插入表中的数量。
3.2、存储过程抽取部分NULL和NOT NULL
下面我将ai.Accounts所有为null的帐户挑出来,同时结合几个not null的帐户,添加到原始存储过程游标定义中
--7not null+4null存储过程
ALTER PROCEDURE [dbo].[DBA_TroubleShooting]
AS
BEGIN
Declare @UserID int,@Accounts varchar(31),@NickName varchar(50),@Score int,@Amount int,@Amount2 int,@Amount3 int
Declare loop_cursor CURSOR FOR
Select T.userID,ai.Accounts,ai.NickName,T.Amount,T.Amount2,T.Amount3,T.Score
from UserFieldsOfJinGui T with(nolock)
left join AccountsInfoSimple ai with(nolock) on T.userID=ai.userID
where T.userid in(5614,5782,6148,22988765,44569254,6834,30536717,7528,26744509,7537,7865)
order by charindex(convert(varchar,T.userid),'5614,5782,6148,22988765,44569254,6834,30536717,7528,26744509,7537,7865')
Open loop_cursor
FETCH NEXT FROM loop_cursor INTO @userID,@Accounts,@NickName,@Amount,@Amount2,@Amount3,@Score
While @@FETCH_STATUS=0
Begin
insert into TortoiseInfo_dba(userID,Accounts,NickName,Amount,Amount2,Amount3,Score,statisticsDate,runDate)
values(@userID,@Accounts,@NickName,@Amount,@Amount2,@Amount3,@Score,Convert(varchar(10),dateadd(day,-1,getdate()),120),getDate())
FETCH NEXT FROM loop_cursor INTO @userID,@Accounts,@NickName,@Amount,@Amount2,@Amount3,@Score
End
Close loop_cursor
Deallocate loop_cursor
END
GO
新建查询,结合XACT_ABORT,查看存储过程执行结果
/****7not null+4null存储过程****/
--查询窗口
EXEC DBA_TroubleShooting --写入7行,所有非空的数据都可以插入
--查询窗口+SET XACT_ABORT ON
SET XACT_ABORT ON --放在存储过程前面,或封装到存储过程里面效果相同
EXEC DBA_TroubleShooting --写入3行,遇到为空的就终止
SET XACT_ABORT OFF
新建作业,分别将查询窗口中的语句放到作业步骤
--作业
已以用户 ** 的身份执行。 不能将值 NULL 插入列 'Accounts',表 '**.dbo.TortoiseInfo_dba';列不允许有空值。INSERT 失败。 [SQLSTATE 23000] (错误 515) 语句已终止。 [SQLSTATE 01000] (错误 3621). 该步骤失败。
写入7行,所有非空的数据都插入
--作业+SET XACT_ABORT ON
已以用户 ** 的身份执行。 不能将值 NULL 插入列 'Accounts',表 '**.dbo.TortoiseInfo_dba';列不允许有空值。INSERT 失败。 [SQLSTATE 23000] (错误 515). 该步骤失败。
写入3行,与查询窗口XACT_ABORT为ON时相同
此时作业在默认情况下却将所有非空的数据都插入到表,不明白前面数据量多的时候为什么只有部分非空的数据插入。
3.3、存储过程中添加TRY-CATCH
/****存储过程带try-catch****/
USE [LK78DB]
GO
ALTER PROCEDURE [dbo].[DBA_TroubleShooting]
AS
BEGIN
Declare @UserID int,@Accounts varchar(31),@NickName varchar(50),@Score int,@Amount int,@Amount2 int,@Amount3 int
Declare loop_cursor CURSOR FOR
Select T.userID,ai.Accounts,ai.NickName,T.Amount,T.Amount2,T.Amount3,T.Score
from UserFieldsOfJinGui T with(nolock)
left join AccountsInfoSimple ai with(nolock) on T.userID=ai.userID
where T.userid in(5614,5782,6148,22988765,44569254,6834,30536717,7528,26744509,7537,7865)
order by charindex(convert(varchar,T.userid),'5614,5782,6148,22988765,44569254,6834,30536717,7528,26744509,7537,7865')
Open loop_cursor
FETCH NEXT FROM loop_cursor INTO @userID,@Accounts,@NickName,@Amount,@Amount2,@Amount3,@Score
While @@FETCH_STATUS=0
Begin
begin try
insert into TortoiseInfo_dba(userID,Accounts,NickName,Amount,Amount2,Amount3,Score,statisticsDate,runDate)
values(@userID,@Accounts,@NickName,@Amount,@Amount2,@Amount3,@Score,Convert(varchar(10),dateadd(day,-1,getdate()),120),getDate())
end try
begin catch
print @UserID print ERROR_MESSAGE()
end catch
FETCH NEXT FROM loop_cursor INTO @userID,@Accounts,@NickName,@Amount,@Amount2,@Amount3,@Score
End
Close loop_cursor
Deallocate loop_cursor
END
GO
下图是在查询窗口执行存储过程返回的消息
查询窗口/作业不管XACT_ABORT为ON还是OFF,insert不满足约束条件,会执行catch中的语句,然后读取游标中的下一条记录,因此非空数据都能插入。
最开始将原始存储过程中整个begin..end放到begin try...end try语句块,并且没有在cacth中输出ERROR_MESSAGE(),结果一遇到null就结束,后面的非空数据不能插入。以为它和SET XACT_ABORT ON有什么关系,同时还发现连续在同一窗口执行两次存储过程,第二次没有任何消息。找了好久,补上ERROR_MESSAGE()才知道原来是游标已存在。参考文章:SQL Server里面如何检查没有释放的游标,用下面语句查看开启的游标
SELECT session_id ,
cursor_id ,
name ,
creation_time ,
is_open
FROM sys.dm_exec_cursors(0)
WHERE is_open = 1;--1为打开,0表示关闭但未释放
折腾那么久好像偏离了主题,还是没明白同一个存储过程,查询窗口执行会将所有非空数据(4W+)插入到表,作业下却只将部分非空数据(2W+)插入到表;而如果我只抽取其中少量数据(包含null与not nul),两者却都把非空数据插入到表。
4、总结
写好的博客还是要时常翻一翻,以读者的角度去阅读,看能否读懂文章。找不到合理的解释,就不要将就着以为它应该是这样或那样。
Exec in Job and NewQuery的更多相关文章
-
VSCode调试go语言出现:exec: ";gcc";: executable file not found in %PATH%
1.问题描述 由于安装VS15 Preview 5,搞的系统由重新安装一次:在用vscdoe编译go语言时,出现以下问题: # odbcexec: "gcc": executabl ...
-
tar 解压bz2报错 Cannot exec: No such file or directory
tar: bzip2: Cannot exec: No such file or directorytar: Error is not recoverable: exiting now 需要安装bzi ...
-
【转】Linux下Fork与Exec使用
Linux下Fork与Exec使用 转自 Linux下Fork与Exec使用 一.引言 对于没有接触过Unix/Linux操作系统的人来说,fork是最难理解的概念之一:它执行一次却返回两个值.for ...
-
Linux学习笔记(8)-exec族函数
昨天学习了Linux下的进程创建,创建一个进程的方法极为简单,只需要调用fork函数就可以创建出一个进程,但是-- 介绍fork()函数的时候提到,在创建进程后,子进程与父进程有相同的代码空间,执行的 ...
-
EXEC sp_executesql
declare @sql nvarchar(max)declare @nu int set @sql='SELECT * FROM [FMTest].[dbo].[FM_Radio_Station]' ...
-
Hive创建表格报【Error, return code 1 from org.apache.hadoop.hive.ql.exec.DDLTask. MetaException】引发的血案
在成功启动Hive之后感慨这次终于没有出现Bug了,满怀信心地打了长长的创建表格的命令,结果现实再一次给了我一棒,报了以下的错误Error, return code 1 from org.apache ...
-
SQL Server 中 EXEC 与 SP_EXECUTESQL 的区别
SQL Server 中 EXEC 与 SP_EXECUTESQL 的区别 MSSQL为我们提供了两种动态执行SQL语句的命令,分别是 EXEC 和 SP_EXECUTESQL ,我们先来看一下两种方 ...
-
js正则表达式中test,exec,match方法的区别
test test 返回 Boolean,查找对应的字符串中是否存在模式.var str = "1a1b1c";var reg = new RegExp("1." ...
-
RegExp类型exec()方法的返回值说明
之前看别人写的插件经常会用到RegExp对象来验证输入,并且获取一些那时我并不晓得是什么东西的数组,来取值进行自定义的逻辑处理.今天学习了一下RegExp类型.终于有了一个初步的了解,至少不会看一会就 ...
随机推荐
-
Maven引入本地jar包
<dependency> <groupId>${gorup}</groupId> <artifactId>${artifact}</artifac ...
-
C#多线程网摘 2
C#中,可以使用Thread类来处理(包含创建,启动,挂起,恢复,终止等操作)线程.本文将介绍如何使用Thread类来创建与启动新线程. Thread类类位于System.Threading命名空间中 ...
-
利用SpannableString设置文本
private void setTips(){ String big = "大字深色"; String small = "小字淡色"; Spannable ti ...
-
关于linux下rar文件的解压缩操作
在linux系统下.本身没有对rar文件操作的命令,如果需要对rar格式的文件操作,我们需要安装第三方的软件rar以及unrar. 1.linux下rar管理软件下载的官方地址为:http://www ...
-
C++中的多重继承与虚继承的问题
1.C++支持多重继承,但是一般情况下,建议使用单一继承. 类D继承自B类和C类,而B类和C类都继承自类A,因此出现下图所示情况: A A \ / B C ...
-
ios 用LLDB查看模拟器文件路径以及一些常用的命令
我看网络上有好多有关lldb调试命令的介绍,我都看了一遍,都没有这个方法,所以我在这里补充出来,帮助需要的人. 另外附上一些 实用LLDB命令 我们可以使用e命令定义变量 (lldb) e NSStr ...
-
Python学习之路——迭代器、生成器、算法基础、正则
一.迭代器: 迭代器是访问集合元素的一种方式. 迭代器对象是从集合的第一个元素开始访问,直到所有的元素被访问完结束. 迭代器只能往前不会后退,不过这也没什么,因为人们很少在迭代途中往后退. 另外,迭代 ...
-
TR069网管测试华为ITMS平台(内部测试使用)
声明:本篇华为ITMS平台仅用于学习和测试使用,如果需要商用,请购买正版软件! 原创作品,转载请注明出处,严禁非法转载或者用于商业目的! email:40879506@qq.com 一. 准备软件 V ...
-
java中Base64的加密工具封装
Base64加密作为最简单普遍的加密方式(其实只能称为编码方式),应用场景众多比如秘钥,安全证书,也应用在其他的加密方式中或与其他加密方式进行嵌套使用 可以通过引用sun.misc来使用,也可以自己手 ...
-
Atitit jquery &#160;1.4--v1.11 &#160;v1.12 &#160;v2.0 &#160;3.0 的新特性
Atitit jquery 1.4--v1.11 v1.12 v2.0 3.0 的新特性 1.1. Jquery1.12 jQuery 2.2 和 1.12 新版本发布 - OPEN资讯.h ...