Что не так с моим 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