Что не так с моим Try Catch в T-SQL?

Я использую SQL Server 2008, и когда я запускаю это выражение в студии управления, оператор Select в Catch Block выполняется как ожидалось

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' 'Invalid Object Name: IDontExist', поэтому зачем обрабатывать его, а другой нет?

Ответ 1

Я не получаю блок CATCH вообще.

Это потому, что код не будет компилироваться, потому что объект не существует, ни один план не генерируется, поэтому ничего не запускается, чтобы попасть в блок CATCH.

Вы никогда не сможете попасть в этот блок catch, так что неправильное использование вашего теста/примера. Вы можете удалять внешний блок catch в другой области (например, вложенные хранимые процедуры)

Изменить: я использую SQL Server 2005 SP3

Это зависит от применения отложенного разрешения имен, связанного с перекомпиляцией уровня инструкции.

  • В моем случае вся партия выходит из строя в оба раза, и не выполняется перекомпиляция уровня инструкций, поэтому нет отложенного разрешения имени

  • В случае OP пакетная компиляция и запуск, но затем имеет ошибку рекомпиляции/отсроченного определения уровня выражения в запущенном коде

Я собираюсь найти некоторые рекомендации о том, почему это другое, поскольку BOL не говорит много, и Erland Sommarskog

Ответ 2

Это укусило меня и в прошлом.

Не все ошибки, сгенерированные внутри операторов блока TRY, передаются в блок CATCH. Любые ошибки с серьезностью 10 или менее считаются предупреждениями и не вызывают контроль потока в блок CATCH. Кроме того, любые ошибки, которые нарушают соединение с базой данных, не приведут к достижению блока CATCH. Могут быть и другие ситуации.

Ответ 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.

Ответ 4

Такое поведение происходит, если ранее у вас была таблица 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, это также будет автоматически параметризовано и они будут вести себя одинаково.

Я не совсем уверен, почему здесь используется автоматическая параметризация. Я думаю, что это объясняется ниже извлечением из BOL.

Следующие типы ошибок не обрабатываются блоком CATCH, когда они происходят на том же уровне выполнения, что и конструкция TRY…CATCH... [те], которые происходят во время перекомпиляции на уровне инструкций... Если ошибка возникает во время компиляции или рекомпиляции на уровне инструкции на более низком уровне выполнения (например, при выполнении sp_executesql или пользовательской хранимой процедуры) внутри блока TRY ошибка  происходит на более низком уровне, чем конструкция TRY…CATCH, и будет обрабатываться связанным блоком CATCH.

Я предполагаю, что автоматическая параметризация этого оператора означает, что он перекомпилируется с более низким уровнем выполнения и увлекателен.

Ответ 5

Теперь у нас есть все объяснения, почему это происходит. Давайте посмотрим на фактическое решение проблемы.

Сначала возьмем утверждения, предложенные выше @d-k-mulligan, и превратим их в сохраненные procs.

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 выше, которые получают объект, который не нашел ошибок.

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'.

ЗАМЕЧАНИЕ: Кальман Тот сделал все трудные исследования здесь: http://www.sqlusa.com/articles2008/trycatch/

Ответ 6

Временное решение с динамическим sql. Возможно, это будет полезно для кого-то.

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