我的T-SQL Try Catch出了什么问题?

时间:2010-12-29 17:33:12

标签: sql-server

我正在使用SQL Server 2008,当我在Management studio中运行此Statement时,Catch Block中的Select语句按预期执行

BEGIN TRY
 INSERT INTO IDontExist(ProductID)
 VALUES(1)
END TRY
BEGIN CATCH
SELECT 'There was an error! ' + ERROR_MESSAGE()
END CATCH

但是,当我运行此语句时,Catch Block中的语句永远不会被执行,而错误只会显示在结果选项卡中

BEGIN TRY
  Select * from IDontExist
END TRY
BEGIN CATCH
  SELECT 'There was an error! ' + ERROR_MESSAGE()
END CATCH

它们都返回相同的错误编号'208''无效的对象名称:IDontExist'所以为什么一个会被处理而另一个没有?

6 个答案:

答案 0 :(得分:6)

我根本没有点击CATCH块。

那是因为代码不会编译,因为对象不存在,没有生成计划,所以没有任何东西可以运行来命中CATCH块。

你可以从不点击这个catch块,所以你的测试/示例有些错误。您可以在不同的范围内命中外部catch块(例如嵌套的存储过程)

编辑:我正在使用SQL Server 2005 SP3

取决于延迟名称解析是否适用,与语句级别重新编译有关。

  • 在我的情况下,整个批处理都失败了两次并且没有语句级别重新编译,所以没有延迟名称解析

  • 在OP的情况下,批处理编译并运行,但在运行代码中有一个语句级重新编译/延迟名称解析错误

鉴于BOL没有多说,Erland Sommarskog

答案 1 :(得分:5)

这也困扰了我。

并非所有在TRY块语句中生成的错误都会传递到CATCH块。严重性为10或更小的任何错误都被视为警告,并且不会导致控制流向CATCH块。此外,任何破坏数据库连接的错误都不会导致访问CATCH块。可能还有其他情况。

答案 2 :(得分:3)

直接来自http://msdn.microsoft.com/en-us/library/ms175976.aspx

USE AdventureWorks2008R2;
GO

BEGIN TRY
    -- Table does not exist; object name resolution
    -- error not caught.
    SELECT * FROM NonexistentTable;
END TRY
BEGIN CATCH
    SELECT 
        ERROR_NUMBER() AS ErrorNumber
        ,ERROR_MESSAGE() AS ErrorMessage;
END CATCH

错误未被捕获,控制权从TRY ... CATCH构造传递到下一个更高级别。

在存储过程中运行SELECT语句将导致错误发生在低于TRY块的级别。该错误将由TRY ... CATCH构造处理。

答案 3 :(得分:3)

如果您之前有一个表IDontExist并为其编制了一个仍在缓存中的计划然后删除该表,则会发生此行为。

即使没有表存在,也会发生两次单独的语句。第一次运行会引发一个未捕获的错误。第二次运行(在缓存第一个计划之后)成功。

/*Clear Cache*/
DBCC FREEPROCCACHE

GO

BEGIN TRY
 INSERT INTO IDontExist(ProductID)
 VALUES(1)
END TRY
BEGIN CATCH
SELECT 'There was an error! ' + ERROR_MESSAGE()
END CATCH

GO
/*Plan now Cached*/

SELECT query_plan
FROM   sys.dm_exec_cached_plans cp
       OUTER APPLY sys.dm_exec_sql_text(plan_handle) t
       OUTER APPLY sys.dm_exec_query_plan(plan_handle) qp
WHERE  t.text LIKE '%IDontExist%'
OPTION (RECOMPILE)

GO

BEGIN TRY
 INSERT INTO IDontExist(ProductID)
 VALUES(1)
END TRY
BEGIN CATCH
SELECT 'There was an error! ' + ERROR_MESSAGE()
END CATCH

GO

INSERT语句自动参数化。

如果您将Select * from IDontExist语句更改为Select * from IDontExist WHERE ProductID = 1,则该语句也会自动参数化,并且它们的行为相同。

我并不完全确定为什么自动参数化在这里有所作为。我认为这可以通过以下extract from BOL进行解释。

  

CATCH块出现在与语句级重新编译期间发生的TRY…CATCH构造... [那些]相同的执行级别时,不会处理以下类型的错误。 ..如果在sp_executesql块内的较低执行级别(例如,执行TRY或用户定义的存储过程时)的编译或语句级重新编译期间发生错误,则会出现错误    发生在比TRY…CATCH构造更低的级别,并且将由关联的CATCH块处理。

我认为该语句的自动参数化意味着它以较低的执行级别重新编译并且可以捕获。

答案 4 :(得分:0)

现在我们已经解释了为什么会发生这种情况。让我们看看问题的实际解决方案。

首先让我们看看@ d-k-mulligan上面提出的陈述并将它们变成存储过程。

IF OBJECT_ID('dbo.prcIDontExistINSERT', 'P') IS NOT NULL DROP PROCEDURE dbo.prcIDontExistINSERT
GO
CREATE PROCEDURE dbo.prcIDontExistINSERT 
AS
BEGIN TRY
 INSERT INTO IDontExist(ProductID)
 VALUES(1)
END TRY
BEGIN CATCH
  SELECT 'There was an error! ' + ERROR_MESSAGE()
END CATCH
GO

IF OBJECT_ID('dbo.prcIDontExistSELECT', 'P') IS NOT NULL DROP PROCEDURE dbo.prcIDontExistSELECT
GO
CREATE PROCEDURE dbo.prcIDontExistSELECT 
AS
BEGIN TRY
  SELECT * FROM IDontExist
END TRY
BEGIN CATCH
  SELECT 'There was an error! ' + ERROR_MESSAGE()
END CATCH
GO

如果我们运行其中任何一个,我们会看到相同的错误。

EXEC dbo.prcIDontExistINSERT
EXEC dbo.prcIDontExistSELECT

Msg 208, Level 16, State 1, Procedure prcIDontExistSELECT, Line 4
Invalid object name 'IDontExist'.

现在的解决方案是创建错误处理包装器procs,其唯一目的是捕获上述原始proc中的任何错误,这些错误会导致找不到对象错误。

IF OBJECT_ID('dbo.prcIDontExistInsert_ERROR_HANDLER', 'P') IS NOT NULL DROP PROCEDURE dbo.prcIDontExistInsert_ERROR_HANDLER
GO
CREATE PROCEDURE dbo.prcIDontExistInsert_ERROR_HANDLER 
AS
BEGIN TRY
 EXEC dbo.prcIDontExistINSERT
END TRY
BEGIN CATCH
  SELECT 'There was an error! ' + ERROR_MESSAGE()
END CATCH
GO

IF OBJECT_ID('dbo.prcIDontExistSELECT_ERROR_HANDLER', 'P') IS NOT NULL DROP PROCEDURE dbo.prcIDontExistSELECT_ERROR_HANDLER
GO
CREATE PROCEDURE dbo.prcIDontExistSELECT_ERROR_HANDLER 
AS
BEGIN TRY
 EXEC dbo.prcIDontExistSELECT
END TRY
BEGIN CATCH
  SELECT 'There was an error! ' + ERROR_MESSAGE()
END CATCH
GO

最后,让我们运行任何一个错误处理过程并查看我们期望的消息。

EXEC dbo.prcIDontExistInsert_ERROR_HANDLER
EXEC dbo.prcIDontExistSELECT_ERROR_HANDLER

There was an error! Invalid object name 'IDontExist'.

注意:Kalman Toth在这里完成了所有艰苦的研究工作: http://www.sqlusa.com/articles2008/trycatch/

答案 5 :(得分:0)

使用动态sql进行解决方法。也许对某人有帮助。

begin try
    exec('
        insert into IDontExist(ProductID)
        values(1)
    ')
end try
begin catch
    select 'There was an error! ' + error_message()
end catch