CATCH块首次执行但第二次执行 - 为什么?

时间:2017-10-12 12:32:32

标签: sql-server sql-server-2014

第一次对脚本进行任何编辑后,catch块不会执行,但从第2次开始工作正常。

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

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

1 个答案:

答案 0 :(得分: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

<强>更新

我尝试使用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