2017-10-12 170 views
0

第一次对脚本进行任何编辑后,catch块都不会执行,但从第二次开始工作正常。CATCH块不会第一次执行,而是第二次执行 - 为什么?

下面是一些脚本来演示问题。

use master 
go 

print 'rollback demo' 

declare @errordemo bit = 1 
select 'Transaction Count Before =', @@TRANCOUNT 
begin try 
    begin transaction 
    if (@errordemo = 0) select 'abc' as column1 into MyTestTable 
    insert into MyTestTable values ('xyz') 
    commit transaction 
end try 
begin catch 
    rollback transaction 
end catch 
go 

select 'Transaction Count After =', @@TRANCOUNT 
go 

-- this is only to bring back the system to its previous state 
if (@@TRANCOUNT > 0) rollback transaction 
go 
if exists(select * from sys.tables where name ='MyTestTable') drop table MyTestTable 
go 

将上述脚本复制到SQL Server Management Studio并运行它。 您将得到以下错误结果。

Transaction Count Before = 0 
Transaction Count After = 1 

按F5一次又一次,它会与成功执行:

Transaction Count Before = 0 
Transaction Count After = 0 

这意味着执行catch块。

现在注释掉第一行print 'rollback demo',或更改其文字。你会再次得到错误。再次按F5任意次数并且没有错误。重复注释该行(或对脚本进行任何其他更改),然后可以看到可预测/可重现的模式。

这是怎么回事?

下面是一些截图,显示正在发生的事情。

当成功:

successful result 1 successful result 2

如果不成功:

unsuccessful result 1 unsuccessful result 2

+0

你抓不到'无效的对象name'错误。 – DavidG

+0

@DavidG,为什么第二次以后没有错误?脚本没有改变,只是重新编译。 –

回答

1

这是因为你的错误是绑定错误不能被捕获在TRY..CATCH块。

当您引用一个不存在的对象时,SQL Server甚至不会尝试检查其列,它不会编译这段代码,而是将它留给执行时间。 这叫做deferred name resolution

只有在执行此语句时,它才会检查表并引发错误。

这是编译错误,不能在同一范围内捕获(仅在外部范围内)。

有一个连接项目,你可以检查: Try-catch should capture the parse errors

因此,实际上当发生此错误时,您的catch块无法到达。

在下次执行时,如果查询中没有一个字符发生更改,则使用缓存的计划。所以它不会在第二次执行时编译。

但是,如果你改变你的查询文本(尝试添加 - 中的任何部分),或者如果您指示不缓存计划中的服务器(使用recompile选项)是这样的:

declare @errordemo bit = 1 
select 'Transaction Count Before =', @@TRANCOUNT 
begin try 
    begin transaction 
    if (@errordemo = 0) select 'abc' as column1 into dbo.MyTestTable 
    print @errordemo 
    insert into dbo.MyTestTable values ('xyz') 
    option (recompile) -------------------------------!!!!!!!!!!!!!!!!!!! 
    commit transaction 
end try 
begin catch 
    print 'catch block' 
    rollback transaction 
end catch 
go 

select 'Transaction Count After =', @@TRANCOUNT 
go 

if (@@TRANCOUNT > 0) rollback transaction 

该计划不会被缓存,并且每次都会编译查询,并且每次都会看到编译错误,并且您的catch块将永远无法到达。

这里是计划缓存:plan

在这里你可以看到,有没有真正的插入方案:

enter image description here

这是计划插入如何真正的样子: enter image description here

UPDATE

我试图重现与SELECT查询相同的结果,并发现计划中的任何差异,但我无法从计划缓存中提取SELECT的计划。 它的条目存在,具有与INSERT计划相同的大小,但不可能看到此计划,似乎它没有被缓存,但条目确实存在...

要重现它,您可以使用下面的代码:

/*select query F7CA8D53-E171-4B5F-8CEA-B19461819C0D*/ 
declare @errordemo bit = 1 
select 'Transaction Count Before =', @@TRANCOUNT 
begin try 
    begin transaction 
    if (@errordemo = 0) select 'abc' as column1 into dbo.MyTestTable 
    print @errordemo 
    select * from MyTestTable 
    commit transaction 
end try 
begin catch 
    print 'catch block' 
    rollback transaction 
end catch 
go 

select 'Transaction Count After =', @@TRANCOUNT 
go 

if (@@TRANCOUNT > 0) rollback transaction; 
go 
------------------------------------------- 
------------------------------------------- 
/*insert query C7D24848-E2BB-46E7-8B1B-334406789CF9*/ 
declare @errordemo bit = 1 
select 'Transaction Count Before =', @@TRANCOUNT 
begin try 
    begin transaction 
    if (@errordemo = 0) select 'abc' as column1 into dbo.MyTestTable 
    print @errordemo 
    insert into MyTestTable values(1) 
    commit transaction 
end try 
begin catch 
    print 'catch block' 
    rollback transaction 
end catch 
go 

select 'Transaction Count After =', @@TRANCOUNT 
go 

if (@@TRANCOUNT > 0) rollback transaction 
go 
----------------------- 
----------------------- 
select * 
from sys.dm_exec_cached_plans p 
cross APPLY sys.dm_exec_query_plan(p.plan_handle) pl 
cross apply sys.dm_exec_sql_text (p.plan_handle) t 
where (t.text like '%F7CA8D53-E171-4B5F-8CEA-B19461819C0D%' -- select 
    or t.text like '%C7D24848-E2BB-46E7-8B1B-334406789CF9%')-- insert 
    and t.text not like '%sys.dm_exec_cached_plans%' 

enter image description here

+0

我知道我们无法捕获绑定或编译错误。但是,当出现编译错误时,该计划不应该被缓存,不是吗? –

+0

是的,它是chached。它缓存时没有用于插入的那部分,因为它已被缓存。我会上传计划,你会看到它 – sepupic

+0

++好的和有益的答案。 –

相关问题