Использование конструкции TRY...CATCH в языке Transact-SQL

Ошибки в коде Transact-SQL можно обрабатывать с помощью конструкции TRY...CATCH, синтаксис которой напоминает обработку исключений на языках Microsoft Visual C++ и Microsoft Visual C#. Конструкция TRY…CATCH состоит из двух частей: блок TRY и блок CATCH. При обнаружении ошибки в инструкции Transact-SQL внутри блока TRY управление передается блоку CATCH, где эта ошибка может быть обработана.

По завершении обработки исключения блоком CATCH управление передается первой инструкции Transact-SQL, следующей за инструкцией END CATCH. Если инструкция END CATCH является последней инструкцией хранимой процедуры или триггера, управление возвращается коду, вызвавшему эту хранимую процедуру или триггер. Инструкции Transact-SQL в блоке TRY, следующие за инструкцией, вызвавшей ошибку, не выполняются.

Если в блоке TRY ошибок нет, управление передается инструкции, следующей непосредственно за связанной с ней инструкцией END CATCH. Если инструкция END CATCH является последней инструкцией хранимой процедуры или триггера, управление передается инструкции, вызвавшей эту хранимую процедуру или триггер.

Блок TRY начинается с инструкции BEGIN TRY и завершается инструкцией END TRY. Между инструкциями BEGIN TRY и END TRY могут быть помещены одна или несколько инструкций Transact-SQL.

За блоком TRY сразу же должен следовать блок CATCH. Блок CATCH начинается с инструкции BEGIN CATCH и завершается инструкцией END CATCH. В Transact-SQL каждый блок TRY связан только с одним блоком CATCH.

Работа с конструкцией TRY…CATCH

При работе с конструкцией TRY…CATCH пользуйтесь следующими правилами и предложениями.

  • Каждая конструкция TRY…CATCH должна находиться в одном пакете, хранимой процедуре или триггере. Например, если блок TRY размещен в одном пакете, размещение блока CATCH в другом пакете не допускается. При выполнении следующего скрипта будет выдано сообщение об ошибке:

    BEGIN TRY
        SELECT *
            FROM sys.messages
            WHERE message_id = 21;
    END TRY
    GO
    -- The previous GO breaks the script into two batches,
    -- generating syntax errors. The script runs if this GO
    -- is removed.
    BEGIN CATCH
        SELECT ERROR_NUMBER() AS ErrorNumber;
    END CATCH;
    GO
    
  • За блоком TRY сразу же должен следовать блок CATCH.

  • Конструкция TRY...CATCH может быть вложенной. Это означает, что конструкция TRY…CATCH может быть размещена внутри других блоков TRY и CATCH. При возникновении ошибки внутри вложенного блока TRY управление программой передается блоку CATCH, связанному с вложенным блоком TRY.

  • Для обработки ошибки внутри данного блока CATCH нужно написать конструкцию TRY...CATCH внутри этого блока CATCH.

  • Ошибки с уровнем серьезности, большим или равным 20, заставляющие компонент Database Engine закрыть соединение, не могут быть обработаны конструкцией TRY…CATCH. Однако конструкция TRY…CATCH обеспечивает обработку ошибок с уровнем серьезности, большим или равным 20, если соединение не закрывается.

  • Ошибки с уровнем серьезности, меньшим или равным 10, рассматриваются как предупреждения или информационные сообщения и не обрабатываются с помощью конструкции TRY…CATCH.

  • Сообщения об ошибке завершают пакет, даже если этот пакет находится внутри области конструкции TRY…CATCH. Это относится и к вызовам, направляемым координатором распределенных транзакций Майкрософт (MS DTC) при ошибке распределенной транзакции. Координатор распределенных транзакций (Microsoft) осуществляет управление распределенными транзакциями.

    ПримечаниеПримечание

    Если распределенная транзакция выполняется в области блока TRY и при этом происходит ошибка, выполнение передается связанному блоку CATCH. Распределенная транзакция переходит в нефиксируемое состояние. Выполнение внутри блока CATCH может быть прервано координатором распределенных транзакций (Майкрософт), который осуществляет управление распределенными транзакциями. При совершении ошибки MS DTC асинхронно уведомляет все участвующие в распределенной транзакции серверы и прекращает выполнение всех задач, относящихся к этой распределенной транзакции. Это уведомление направляется в форме предупреждения, которое не обрабатывается конструкцией TRY…CATCH, а выполнение пакета прекращается. Когда выполнение пакета заканчивается, компонентом Database Engine будет выполнен откат любых активных нефиксируемых транзакций. Если при переходе транзакции в нефиксируемое состояние не было отправлено сообщения об ошибке, оно будет отправлено клиентскому приложению после завершения выполнения пакета, в сообщении будет указано, что была обнаружена нефиксируемая транзакция и выполнен ее откат. Дополнительные сведения о распределенных транзакциях см. в разделе Распределенные транзакции (компонент Database Engine).

Функции ошибок

В конструкции TRY…CATCH для сбора сведений об ошибках используются следующие функции.

  • ERROR_NUMBER() возвращает номер ошибки.

  • Функция ERROR_MESSAGE() возвращает полный текст сообщения об ошибке. Текст содержит значения подставляемых параметров, таких как длина, имена объектов или время.

  • ERROR_SEVERITY() возвращает уровень серьезности ошибки.

  • Функция ERROR_STATE() возвращает код состояния ошибки.

  • Функция ERROR_LINE() возвращает номер строки, которая вызвала ошибку, внутри подпрограммы.

  • Функция ERROR_PROCEDURE() возвращает имя хранимой процедуры или триггера, в котором произошла ошибка.

Сведения об ошибках извлекаются с помощью этих функций из любого источника в области блока CATCH конструкции TRY…CATCH. Функции ошибок возвращают значение NULL, если они вызываются вне области блока CATCH. Ссылки на функции ошибок могут содержаться внутри хранимой процедуры. Эти функции можно использовать для получения сведений об ошибках при выполнении хранимой процедуры внутри блока CATCH. В этом случае повторять код обработки ошибок в каждом блоке CATCH нет необходимости. В следующем примере кода инструкция SELECT в блоке TRY формирует ошибку деления на 0. Ошибка обрабатывается блоком CATCH, который возвращает сведения об ошибке с помощью хранимой процедуры.

USE AdventureWorks2008R2;
GO

-- Verify that the stored procedure does not exist.
IF OBJECT_ID ('usp_GetErrorInfo', 'P') IS NOT NULL
    DROP PROCEDURE usp_GetErrorInfo;
GO

-- Create a procedure to retrieve error information.
CREATE PROCEDURE usp_GetErrorInfo
AS
    SELECT 
        ERROR_NUMBER() AS ErrorNumber,
        ERROR_SEVERITY() AS ErrorSeverity,
        ERROR_STATE() as ErrorState,
        ERROR_PROCEDURE() as ErrorProcedure,
        ERROR_LINE() as ErrorLine,
        ERROR_MESSAGE() as ErrorMessage;
GO

BEGIN TRY
    -- Generate divide-by-zero error.
    SELECT 1/0;
END TRY
BEGIN CATCH
    -- Execute the error retrieval routine.
    EXECUTE usp_GetErrorInfo;
END CATCH;
GO

Ошибки компиляции и ошибки повторной компиляции уровня инструкции

Существует два типа ошибок, не обрабатываемых конструкцией TRY…CATCH в случае, если ошибка происходит на том же уровне выполнения, что и конструкция TRY…CATCH.

  • Ошибки компиляции, такие как синтаксические ошибки, препятствующие выполнению пакета.

  • Ошибки, происходящие во время повторной компиляции уровня инструкций, такие как ошибки разрешения имен объектов, которые происходят после компиляции из-за отложенного разрешения имен.

Если содержащий конструкцию TRY…CATCH пакет, хранимая процедура или триггер формирует одну из данных ошибок, конструкция TRY…CATCH не обрабатывает эти ошибки. Данные ошибки возвращаются приложению или пакету, который вызвал завершившуюся ошибкой процедуру. Например, в следующем примере кода показана инструкция SELECT, вызывающая синтаксическую ошибку. При выполнении этого примера в редакторе запросов среды Среда SQL Server Management Studio выполнение не будет начато из-за невозможности компиляции пакета. Ошибка будет возвращена в редактор запросов и не будет выявлена конструкцией TRY…CATCH.

USE AdventureWorks2008R2;
GO

BEGIN TRY
    -- This PRINT statement will not run because the batch
    -- does not begin execution.
    PRINT N'Starting execution';

    -- This SELECT statement contains a syntax error that
    -- stops the batch from compiling successfully.
    SELECT ** FROM HumanResources.Employee;
END TRY
BEGIN CATCH
    SELECT 
        ERROR_NUMBER() AS ErrorNumber,
        ERROR_MESSAGE() AS ErrorMessage;
END CATCH;
GO

В отличие от синтаксической ошибки, приведенной в предыдущем примере, ошибка, происходящая при повторной компиляции на уровне инструкции, не будет препятствовать компиляции пакета, но она завершит обработку пакета, как только произойдет сбой повторной компиляции данной инструкции. Например, если пакет содержит две инструкции и вторая инструкция ссылается на несуществующую таблицу, благодаря отложенному разрешению имен компиляция пакета завершится успешно и начнется выполнение без привязки несуществующей таблицы к плану запроса до повторной компиляции данной инструкции. При передаче управления инструкции, содержащей ссылку на несуществующую таблицу, выполнение пакета прекращается с ошибкой. Этот тип ошибок не обрабатывается конструкцией TRY…CATCH на том же уровне выполнения, на котором произошла ошибка. Следующий пример демонстрирует эту ситуацию.

USE AdventureWorks2008R2;
GO

BEGIN TRY
    -- This PRINT statement will run because the error
    -- occurs at the SELECT statement.
    PRINT N'Starting execution';

    -- This SELECT statement will generate an object name
    -- resolution error because the table does not exist.
    SELECT * FROM NonExistentTable;
END TRY
BEGIN CATCH
    SELECT 
        ERROR_NUMBER() AS ErrorNumber,
        ERROR_MESSAGE() AS ErrorMessage;
END CATCH;
GO

Возможна обработка ошибок средствами конструкции TRY…CATCH в ходе компиляции или повторной компиляции на уровне инструкций. Для этого вызывающий ошибку код необходимо выполнить в отдельном пакете внутри блока TRY. Например, это можно сделать, поместив данный код в хранимую процедуру или выполнив динамическую инструкцию Transact-SQL с помощью процедуры sp_executesql. В таком случае конструкция TRY…CATCH сможет перехватить ошибку на более высоком уровне исполнения, чем уровень, на котором произошла ошибка. Например, в следующем примере кода включена хранимая процедура, создающая ошибку разрешения имен объектов. Содержащий конструкцию TRY…CATCH пакет выполняется на более высоком уровне, чем хранимая процедура, а ошибка, совершенная на более низком уровне, перехватывается.

USE AdventureWorks2008R2;
GO

-- Verify that the stored procedure does not already exist.
IF OBJECT_ID ('usp_MyError', 'P') IS NOT NULL
    DROP PROCEDURE usp_MyError;
GO

CREATE PROCEDURE usp_MyError
AS
    -- This SELECT statement will generate
    -- an object name resolution error.
    SELECT * FROM NonExistentTable;
GO

BEGIN TRY
    -- Run the stored procedure.
    EXECUTE usp_MyError;
END TRY
BEGIN CATCH
    SELECT 
        ERROR_NUMBER() AS ErrorNumber,
        ERROR_MESSAGE() AS ErrorMessage;
END CATCH;
GO

Ниже приводится результирующий набор.

ErrorNumber ErrorMessage
----------- ---------------------------------------
208         Invalid object name 'NonExistentTable'.

Дополнительные сведения см. в разделе Отложенное разрешение и компиляция имен и подразделе «Повторная компиляция планов выполнения» раздела Кэширование и повторное использование плана выполнения.

Нефиксируемые транзакции

Внутри конструкции TRY…CATCH транзакции могут переходить в состояние, в котором транзакция остается открытой, но не может быть зафиксирована. Транзакция не может выполнять действия, влекущие за собой запись в журнал транзакций, такие как изменение данных или попытка отката до точки сохранения. Однако в этом состоянии полученные транзакцией блокировки сохраняются, а соединение тоже остается открытым. Результаты выполнения транзакции не будут отменены до тех пор, пока не будет выполнена инструкция ROLLBACK или пока не завершится выполнение пакета, после чего компонентом Database Engine будет выполнен автоматический откат транзакции. Если при переходе транзакции в нефиксируемое состояние не было отправлено сообщения об ошибке, оно будет отправлено клиентскому приложению после завершения выполнения пакета. В сообщении будет указано, что была обнаружена нефиксируемая транзакция и выполнен ее откат.

Транзакция переходит в нефиксируемое состояние, если внутри блока TRY происходит ошибка, которая в других обстоятельствах завершила бы эту транзакцию. Например, в результате большинства ошибок, вызываемых инструкциями языка DDL, такими как CREATE TABLE, и большинства ошибок, возникающих, когда значение параметра SET XACT_ABORT установлено в ON, транзакции вне блока TRY завершаются, а внутри блока TRY переходят в нефиксируемое состояние.

Код внутри блока CATCH должен проверять состояние транзакции с помощью функции XACT_STATE. Функция XACT_STATE возвращает значение -1, если в сеансе имеется нефиксируемая транзакция. Блок CATCH не должен выполнять действий, которые приведут к записям в журнал, если параметр XACT_STATE возвращает значение -1. В следующем примере кода формируется ошибка инструкции DDL и выполняется проверка состояния транзакции с помощью параметра XACT_STATE для определения наиболее приемлемых действий.

USE AdventureWorks2008R2;
GO

-- Verify that the table does not exist.
IF OBJECT_ID (N'my_books', N'U') IS NOT NULL
    DROP TABLE my_books;
GO

-- Create table my_books.
CREATE TABLE my_books
    (
    Isbn        int PRIMARY KEY,
    Title       NVARCHAR(100)
    );
GO

BEGIN TRY
    BEGIN TRANSACTION;
        -- This statement will generate an error because the 
        -- column author does not exist in the table.
        ALTER TABLE my_books
            DROP COLUMN author;
    -- If the DDL statement succeeds, commit the transaction.
    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    SELECT
        ERROR_NUMBER() as ErrorNumber,
        ERROR_MESSAGE() as ErrorMessage;

    -- Test XACT_STATE for 1 or -1.
    -- XACT_STATE = 0 means there is no transaction and
    -- a commit or rollback operation would generate an error.

    -- Test whether the transaction is uncommittable.
    IF (XACT_STATE()) = -1
    BEGIN
        PRINT
            N'The transaction is in an uncommittable state. ' +
            'Rolling back transaction.'
        ROLLBACK TRANSACTION;
    END;

    -- Test whether the transaction is active and valid.
    IF (XACT_STATE()) = 1
    BEGIN
        PRINT
            N'The transaction is committable. ' +
            'Committing transaction.'
        COMMIT TRANSACTION;   
    END;
END CATCH;
GO

Обработка взаимоблокировок

Конструкция TRY…CATCH может быть использована для обработки взаимоблокировок. Ошибка 1205 жертвы взаимоблокировки может быть перехвачена блоком CATCH, и для данной транзакции может быть выполнен откат до момента разблокирования потоков. Дополнительные сведения о взаимоблокировках см. в разделе Взаимоблокировка.

В следующем примере показано, как конструкция TRY…CATCH может использоваться для обработки взаимоблокировок. В этом первом разделе создается таблица, которая будет использоваться для демонстрации состояния взаимоблокировки, и хранимая процедура, с помощью которой будут выводиться на печать сведения об ошибке.

USE AdventureWorks2008R2;
GO

-- Verify that the table does not exist.
IF OBJECT_ID (N'my_sales',N'U') IS NOT NULL
    DROP TABLE my_sales;
GO

-- Create and populate the table for deadlock simulation.
CREATE TABLE my_sales 
    (
    Itemid       INT PRIMARY KEY,
    Sales        INT not null
    );
GO

INSERT my_sales (itemid, sales) VALUES (1, 1);
INSERT my_sales (itemid, sales) VALUES (2, 1);
GO
  
-- Verify that the stored procedure for error printing
-- does not exist.
IF OBJECT_ID (N'usp_MyErrorLog',N'P') IS NOT NULL
    DROP PROCEDURE usp_MyErrorLog;
GO

-- Create a stored procedure for printing error information.
CREATE PROCEDURE usp_MyErrorLog
AS
    PRINT 
        'Error ' + CONVERT(VARCHAR(50), ERROR_NUMBER()) +
        ', Severity ' + CONVERT(VARCHAR(5), ERROR_SEVERITY()) +
        ', State ' + CONVERT(VARCHAR(5), ERROR_STATE()) + 
        ', Line ' + CONVERT(VARCHAR(5), ERROR_LINE());
    PRINT 
        ERROR_MESSAGE();
GO

Следующие сценарии кода для сеанса 1 и сеанса 2 выполняются одновременно с использованием двух отдельных соединений среды Среда SQL Server Management Studio. В ходе обоих сеансов предпринимаются попытки обновить одни и те же строки таблицы. Один из сеансов успешно проведет операцию обновления с первой попытки, а второй будет выбран жертвой взаимоблокировки. Ошибка жертвы взаимоблокировки приведет к тому, что управление внезапно будет передано блоку CATCH и транзакция перейдет в нефиксируемое состояние. Внутри блока CATCH жертва взаимоблокировки может выполнить откат транзакции и вновь пытаться обновить таблицу до тех пор, пока процесс обновления не завершится успешно или пока не будет исчерпан лимит попыток. К прекращению попыток обновления таблицы приведет то из перечисленных событий, которое наступит первым.

Сеанс 1

Сеанс 2

USE AdventureWorks2008R2;
GO
-- Declare and set variable
-- to track number of retries
-- to try before exiting.
DECLARE @retry INT;
SET @retry = 5;
-- Keep trying to update 
-- table if this task is 
-- selected as the deadlock 
-- victim.
WHILE (@retry > 0)
BEGIN
    BEGIN TRY
        BEGIN TRANSACTION;
    
        UPDATE my_sales
        SET sales = sales + 1
        WHERE itemid = 1;
        WAITFOR DELAY '00:00:13';
    
        UPDATE my_sales
        SET sales = sales + 1
        WHERE itemid = 2;
        SET @retry = 0;
        COMMIT TRANSACTION;
    END TRY
    BEGIN CATCH 
        -- Check error number.
        -- If deadlock victim error,
        -- then reduce retry count
        -- for next update retry. 
        -- If some other error
        -- occurred, then exit
        -- retry WHILE loop.
        IF (ERROR_NUMBER() = 1205)
            SET @retry = @retry - 1;
        ELSE
            SET @retry = -1;
        -- Print error information.
        EXECUTE usp_MyErrorLog;
  
        IF XACT_STATE() <> 0
            ROLLBACK TRANSACTION;
    END CATCH;
END; -- End WHILE loop.
GO
USE AdventureWorks2008R2;
GO
-- Declare and set variable
-- to track number of retries
-- to try before exiting.
DECLARE @retry INT;
SET @retry = 5;
--Keep trying to update 
-- table if this task is 
-- selected as the deadlock 
-- victim.
WHILE (@retry > 0)
BEGIN
    BEGIN TRY
       BEGIN TRANSACTION;
    
        UPDATE my_sales
        SET sales = sales + 1
        WHERE itemid = 2;
        WAITFOR DELAY '00:00:07';
    
        UPDATE my_sales
        SET sales = sales + 1
        WHERE itemid = 1;
        SET @retry = 0;
        COMMIT TRANSACTION;
    END TRY
    BEGIN CATCH 
        -- Check error number.
        -- If deadlock victim error,
        -- then reduce retry count
        -- for next update retry. 
        -- If some other error
        -- occurred, then exit
        -- retry WHILE loop.
        IF (ERROR_NUMBER() = 1205)
            SET @retry = @retry - 1;
        ELSE
            SET @retry = -1;
        -- Print error information.
        EXECUTE usp_MyErrorLog;
  
        IF XACT_STATE() <> 0
            ROLLBACK TRANSACTION;
    END CATCH;
END; -- End WHILE loop.
GO

Использование конструкции TRY…CATCH с инструкцией RAISERROR

Инструкция RAISERROR может быть использована как в блоке TRY, так и в блоке CATCH конструкции TRY…CATCH для управления процессом обработки ошибок.

При выполнении инструкции RAISERROR с уровнем серьезности от 11 до 19 внутри блока TRY управление переходит к связанному с ним блоку CATCH. При выполнении инструкции RAISERROR с уровнем серьезности от 11 до 19 внутри блока CATCH инструкция возвращает вызывающему приложению или пакету сообщение об ошибке. Таким образом, инструкция RAISERROR может быть использована для возвращения вызывающему объекту сведений об ошибке, приведшей к выполнению блока CATCH. Сведения об ошибках, предоставляемые функциями ошибок конструкции TRY…CATCH, могут быть включены в сообщение инструкции RAISERROR. В них может быть включен исходный номер ошибки, однако номер ошибки для инструкции RAISERROR должен быть >= 50000.

При выполнении инструкции RAISERROR с уровнем серьезности, меньшим или равным 10, она возвращает вызывающему пакету или приложению информационное сообщение без вызова блока CATCH.

Инструкция RAISERROR с уровнем серьезности, большим или равным 20, закрывает соединение с базой данных без вызова блока CATCH.

Следующий пример кода иллюстрирует возможное использование инструкции RAISERROR внутри блока CATCH для возвращения исходных сведений об ошибке вызывающему приложению или пакету. Инструкция DELETE, выполняемая хранимой процедурой usp_GenerateError внутри блока TRY, вызывает ошибку нарушения ограничения. Эта ошибка вызывает передачу управления связанному блоку CATCH внутри процедуры usp_GenerateError, в которой выполняется хранимая процедура usp_RethrowError, с целью вывода сведений об ошибке нарушения ограничения при помощи инструкции RAISERROR. Эта созданная инструкцией RAISERROR ошибка возвращается вызывающему пакету, в котором выполнялась хранимая процедура usp_GenerateError, и передает управление связанному блоку CATCH в вызывающем пакете.

ПримечаниеПримечание

Инструкция RAISERROR может формировать только ошибки с состоянием от 1 до 127 включительно. Поскольку компонентом Database Engine могут выводиться ошибки, имеющие состояние 0, рекомендуется проверять состояние ошибки с помощью функции ERROR_STATE перед его передачей в качестве значения параметру состояния инструкции RAISERROR.

USE AdventureWorks2008R2;
GO

-- Verify that stored procedure does not exist.
IF OBJECT_ID (N'usp_RethrowError',N'P') IS NOT NULL
    DROP PROCEDURE usp_RethrowError;
GO

-- Create the stored procedure to generate an error using 
-- RAISERROR. The original error information is used to
-- construct the msg_str for RAISERROR.
CREATE PROCEDURE usp_RethrowError AS
    -- Return if there is no error information to retrieve.
    IF ERROR_NUMBER() IS NULL
        RETURN;

    DECLARE 
        @ErrorMessage    NVARCHAR(4000),
        @ErrorNumber     INT,
        @ErrorSeverity   INT,
        @ErrorState      INT,
        @ErrorLine       INT,
        @ErrorProcedure  NVARCHAR(200);

    -- Assign variables to error-handling functions that 
    -- capture information for RAISERROR.
    SELECT 
        @ErrorNumber = ERROR_NUMBER(),
        @ErrorSeverity = ERROR_SEVERITY(),
        @ErrorState = ERROR_STATE(),
        @ErrorLine = ERROR_LINE(),
        @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');

    -- Build the message string that will contain original
    -- error information.
    SELECT @ErrorMessage = 
        N'Error %d, Level %d, State %d, Procedure %s, Line %d, ' + 
            'Message: '+ ERROR_MESSAGE();

    -- Raise an error: msg_str parameter of RAISERROR will contain
    -- the original error information.
    RAISERROR 
        (
        @ErrorMessage, 
        @ErrorSeverity, 
        1,               
        @ErrorNumber,    -- parameter: original error number.
        @ErrorSeverity,  -- parameter: original error severity.
        @ErrorState,     -- parameter: original error state.
        @ErrorProcedure, -- parameter: original error procedure name.
        @ErrorLine       -- parameter: original error line number.
        );
GO

-- Verify that stored procedure does not exist.
IF OBJECT_ID (N'usp_GenerateError',N'P') IS NOT NULL
    DROP PROCEDURE usp_GenerateError;
GO

-- Create a stored procedure that generates a constraint violation
-- error. The error is caught by the CATCH block where it is 
-- raised again by executing usp_RethrowError.
CREATE PROCEDURE usp_GenerateError 
AS 
    BEGIN TRY
        -- A FOREIGN KEY constraint exists on the table. This 
        -- statement will generate a constraint violation error.
        DELETE FROM Production.Product
            WHERE ProductID = 980;
    END TRY
    BEGIN CATCH
        -- Call the procedure to raise the original error.
        EXEC usp_RethrowError;
    END CATCH;
GO

-- In the following batch, an error occurs inside 
-- usp_GenerateError that invokes the CATCH block in
-- usp_GenerateError. RAISERROR inside this CATCH block
-- generates an error that invokes the outer CATCH
-- block in the calling batch.
BEGIN TRY  -- outer TRY
    -- Call the procedure to generate an error.
    EXECUTE usp_GenerateError;
END TRY
BEGIN CATCH  -- Outer CATCH
    SELECT
        ERROR_NUMBER() as ErrorNumber,
        ERROR_MESSAGE() as ErrorMessage;
END CATCH;
GO

Изменение хода выполнения

Для изменения хода выполнения можно использовать инструкцию GOTO внутри блока TRY или блока CATCH. Инструкцию GOTO можно также использовать для выхода из блока TRY или из блока CATCH; однако GOTO нельзя использовать для входа в блок TRY или в блок CATCH.

Решение обработки ошибок в образце базы данных AdventureWorks2008R2

В состав образца базы данных База данных AdventureWorks2008R2 входит решение обработки ошибок, предназначенное для регистрации сведений об ошибках, перехваченных блоком CATCH конструкции TRY…CATCH, в отношении которых в дальнейшем могут выполняться запросы и осуществляться анализ.

Таблица dbo.ErrorLog

В таблицу ErrorLog заносятся сведения об ошибках, в том числе номер ошибки, уровень серьезности ошибки, состояние ошибки, имя хранимой процедуры или триггера, в котором произошла ошибка, номер строки, в которой произошла ошибка, а также полный текст сообщения об ошибке. Кроме того, в нее записываются дата и время, в которое была совершена ошибка, а также имя пользователя, который выполнял вызвавшую ошибку процедуру. Эта таблица заполняется в ходе выполнения хранимой процедуры uspLogError внутри области действия блока CATCH конструкции TRY…CATCH.

dbo.uspLogError

Хранимая процедура uspLogError заносит в таблицу ErrorLog сведения об ошибке, вызвавшей передачу управления блоку CATCH конструкции TRY…CATCH. Чтобы процедура uspLogError могла занести в таблицу ErrorLog сведения об ошибке, должны быть выполнены следующие условия.

  • Процедура uspLogError должна выполняться в области блока CATCH.

  • Если текущая транзакция находится в нефиксируемом состоянии, необходимо выполнить ее откат до выполнения процедуры uspLogError.

Выходной параметр @ErrorLogID процедуры uspLogError должен возвращать идентификатор ErrorLogID строки, введенной процедурой uspLogError в таблицу ErrorLog. Значение параметра @ErrorLogID по умолчанию равно 0. В следующем примере показан код процедуры uspLogError.

CREATE PROCEDURE [dbo].[uspLogError] 
    @ErrorLogID [int] = 0 OUTPUT  -- Contains the ErrorLogID of the row inserted
                                  -- by uspLogError in the ErrorLog table.

AS
BEGIN
    SET NOCOUNT ON;

    -- Output parameter value of 0 indicates that error 
    -- information was not logged.
    SET @ErrorLogID = 0;

    BEGIN TRY
        -- Return if there is no error information to log.
        IF ERROR_NUMBER() IS NULL
            RETURN;

        -- Return if inside an uncommittable transaction.
        -- Data insertion/modification is not allowed when 
        -- a transaction is in an uncommittable state.
        IF XACT_STATE() = -1
        BEGIN
            PRINT 'Cannot log error since the current transaction is in an uncommittable state. ' 
                + 'Rollback the transaction before executing uspLogError in order to successfully log error information.';
            RETURN;
        END;

        INSERT [dbo].[ErrorLog] 
            (
            [UserName], 
            [ErrorNumber], 
            [ErrorSeverity], 
            [ErrorState], 
            [ErrorProcedure], 
            [ErrorLine], 
            [ErrorMessage]
            ) 
        VALUES 
            (
            CONVERT(sysname, CURRENT_USER), 
            ERROR_NUMBER(),
            ERROR_SEVERITY(),
            ERROR_STATE(),
            ERROR_PROCEDURE(),
            ERROR_LINE(),
            ERROR_MESSAGE()
            );

        -- Pass back the ErrorLogID of the row inserted
        SELECT @ErrorLogID = @@IDENTITY;
    END TRY
    BEGIN CATCH
        PRINT 'An error occurred in stored procedure uspLogError: ';
        EXECUTE [dbo].[uspPrintError];
        RETURN -1;
    END CATCH
END; 

dbo.uspPrintError

Хранимая процедура uspPrintError выводит на печать сведения об ошибке, вызвавшей передачу управления блоку CATCH конструкции TRY…CATCH. Процедуру uspPrintError следует выполнять в области блока CATCH, иначе она не выполняет распечатку сведений об ошибке. В следующем примере показан код процедуры uspPrintError.

CREATE PROCEDURE [dbo].[uspPrintError] 
AS
BEGIN
    SET NOCOUNT ON;

    -- Print error information. 
    PRINT 'Error ' + CONVERT(varchar(50), ERROR_NUMBER()) +
          ', Severity ' + CONVERT(varchar(5), ERROR_SEVERITY()) +
          ', State ' + CONVERT(varchar(5), ERROR_STATE()) + 
          ', Procedure ' + ISNULL(ERROR_PROCEDURE(), '-') + 
          ', Line ' + CONVERT(varchar(5), ERROR_LINE());
    PRINT ERROR_MESSAGE();
END;

Пример обработки ошибки

Следующий пример демонстрирует решение обработки ошибок База данных AdventureWorks2008R2. Код внутри блока TRY пытается удалить из таблицы Production.Product запись с кодом ProductID 980. Ограничение FOREIGN KEY для таблицы препятствует успешному выполнению операции DELETE. Возникает ошибка нарушения ограничения. В результате этой ошибки управление передается блоку CATCH. Внутри блока CATCH выполняются следующие действия.

  • Процедура uspPrintError выводит на печать сведения об ошибке.

  • После выполнения отката транзакции процедура uspLogError вводит сведения об ошибке в таблицу ErrorLog и возвращает идентификатор ErrorLogID вставленной строки в параметр @ErrorLogID OUTPUT.

USE AdventureWorks2008R2;
GO

-- Variable to store ErrorLogID value of the row
-- inserted in the ErrorLog table by uspLogError 
DECLARE @ErrorLogID INT;

BEGIN TRY
    BEGIN TRANSACTION;

    -- A FOREIGN KEY constraint exists on this table. This 
    -- statement will generate a constraint violation error.
    DELETE FROM Production.Product
        WHERE ProductID = 980;

    -- If the delete operation succeeds, commit the transaction.
    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    -- Call procedure to print error information.
    EXECUTE dbo.uspPrintError;

    -- Roll back any active or uncommittable transactions before
    -- inserting information in the ErrorLog.
    IF XACT_STATE() <> 0
    BEGIN
        ROLLBACK TRANSACTION;
    END

    EXECUTE dbo.uspLogError @ErrorLogID = @ErrorLogID OUTPUT;
END CATCH; 

-- Retrieve logged error information.
SELECT * FROM dbo.ErrorLog WHERE ErrorLogID = @ErrorLogID;
GO

Пример вложенной обработки ошибок

Следующий пример демонстрирует использование вложенной конструкции TRY…CATCH.

BEGIN TRY
    BEGIN TRY
        SELECT CAST('invalid_date' AS datetime)
    END TRY
    BEGIN CATCH 
        PRINT 'Inner TRY error number: ' +    
            CONVERT(varchar,ERROR_NUMBER()) + ' on line: ' +
            CONVERT(varchar, ERROR_LINE())
    END CATCH
    SELECT CAST('invalid_int' AS int)
END TRY
BEGIN CATCH
    PRINT 'Outer TRY error mumber: ' + CONVERT(varchar,ERROR_NUMBER())+ 
            ' on line: ' + CONVERT(varchar, ERROR_LINE())
END CATCH

Ниже приводится результирующий набор.

Inner TRY error number: 241 on line: 3

Outer TRY error number: 245 on line: 9

См. также

Справочник

Основные понятия