第一次对脚本进行任何编辑后,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任意次数,没有错误。通过取消注释该行(或在脚本中进行任何其他更改)重复,您可以看到可预测/可重现的模式。
这里发生了什么?
下面是一些屏幕截图,展示了正在发生的事情。
成功时:
不成功时:
答案 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
在这里你可以看到没有真正的插入计划:
<强>更新强>
我尝试使用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%'