TRY...CATCH를 사용하여 오류 처리하기

완료됨

팁 (조언)

자세한 내용은 텍스트 및 이미지 탭을 참조하세요.

프로덕션 데이터베이스 애플리케이션은 예기치 않은 상황을 정상적으로 처리해야 합니다. 0으로 나누기, 제약 조건 위반, 연결 시간 제한 및 잘못된 데이터가 모두 오류를 일으킬 수 있습니다. 처리되지 않은 오류로 인해 불분명한 오류 메시지, 불완전한 트랜잭션 또는 애플리케이션 크래시가 발생합니다. 적절한 오류 처리는 T-SQL 코드가 예측 가능하게 실패하고 의미 있는 피드백을 제공합니다.

데이터베이스 작업은 여러 사용자, 외부 시스템 및 예측할 수 없는 데이터 입력과 동시에 상호 작용합니다. 다시 시도하여 실패한 작업에서 복구할 수 있는 애플리케이션 코드와 달리 데이터베이스 오류는 일부 행이 삽입되고 다른 행이 삽입되지 않거나 잠금이 무기한 유지되는 일관성 없는 상태로 데이터를 남길 수 있습니다. 오류 처리는 이러한 혼란스러운 오류 모드를 제어되고 예측 가능한 응답으로 변환합니다.

잘 디자인된 오류 처리는 다음과 같은 여러 가지 방법으로 코드를 개선합니다.

  • 데이터 무결성 보호: 작업이 중간에 실패할 경우, 적절한 오류 처리를 통해 모든 변경 사항이 함께 커밋되거나 전혀 반영되지 않도록 보장합니다. 이것이 없으면 다단계 프로세스가 데이터베이스에 고아 레코드, 불일치 합계, 또는 끊어진 관계를 남길 수 있습니다.

  • 디버깅 효율성: 줄 번호, 프로시저 이름 및 특정 오류 메시지와 같은 오류 세부 정보를 캡처하면 문제 해결이 더 빨라집니다. 로그를 통해 모호한 오류를 검색하는 대신 오류가 발생한 정확한 위치와 이유를 정확히 파악할 수 있습니다.

  • 사용자 환경: 애플리케이션은 암호화된 데이터베이스 오류 대신 "제품 ID가 존재하지 않습니다"와 같은 의미 있는 메시지를 표시할 수 있습니다. 이렇게 하면 사용자가 무엇이 잘못되었는지와 문제를 해결하는 방법을 이해할 수 있습니다.

  • 운영 가시성: 전용 테이블에 대한 오류 로깅은 성능 문제를 제안하는 버그 또는 시간 제한 오류를 나타내는 되풀이 제약 조건 위반과 같은 패턴을 식별하는 데 도움이 되는 감사 내역을 만듭니다.

  • 우아한 성능 저하: 한 작업이 실패할 경우, 오류 처리로 전체 일괄 처리나 저장 프로시저가 충돌하지 않도록 하고, 대신 코드의 나머지가 계속 실행되거나 대안을 취할 수 있게 합니다.

T-SQL 오류 처리 구현

T-SQL은 다른 프로그래밍 언어의 예외 처리와 유사하게 블록을 통해 TRY...CATCH 구조적 오류 처리를 제공합니다. 블록에서 TRY 오류가 발생하면 실행은 오류를 적절하게 처리할 CATCH 수 있는 블록으로 전송됩니다.

BEGIN TRY
    -- TRY block contains code that might cause an error
    -- If an error occurs here, execution jumps to the CATCH block
    SELECT 1/0;  -- This causes a division by zero error
END TRY
BEGIN CATCH
    -- CATCH block handles the error
    -- This code runs only if an error occurred in the TRY block
    PRINT 'An error occurred';
END CATCH;

오류 처리가 없으면 동일한 코드가 오류 메시지와 함께 종료됩니다.

SELECT 1/0;  -- Msg 8134: Divide by zero error encountered

비고

TRY...CATCH 는 모든 오류를 포착할 수 없습니다. 컴파일 오류(구문 오류, 누락된 개체) 및 연결을 닫는 심각도 20 이상의 오류는 동일한 세션 내에서 포착할 수 없습니다.

오류 정보 검색

CATCH 블록 내에서 SQL Server는 발생한 오류에 대한 세부 정보를 검색하는 다음 함수를 제공합니다.

기능 Description
ERROR_NUMBER() 오류 번호를 반환합니다.
ERROR_MESSAGE() 전체 오류 메시지 텍스트를 반환합니다.
ERROR_SEVERITY() 오류 심각도(0-25)를 반환합니다.
ERROR_STATE() 오류 상태 번호를 반환합니다.
ERROR_LINE() 오류가 발생한 줄 번호를 반환합니다.
ERROR_PROCEDURE() 저장 프로시저 또는 트리거의 이름을 반환합니다.

다음 예제에서는 오류 세부 정보를 캡처하고 나중에 분석하기 위해 테이블에 기록하는 방법을 보여 줍니다.

BEGIN TRY
    -- Attempt an operation that might fail
    INSERT INTO SalesLT.Customer (CustomerID, FirstName, LastName)
    VALUES (1, 'Test', 'Customer');  -- Duplicate key causes error
END TRY
BEGIN CATCH
    -- Log error details to a table using the ERROR_* functions
    INSERT INTO ErrorLog (
        ErrorTime,
        ErrorNumber,
        ErrorSeverity,
        ErrorState,
        ErrorProcedure,
        ErrorLine,
        ErrorMessage
    )
    VALUES (
        GETDATE(),
        ERROR_NUMBER(),       -- The error number (e.g., 2627 for duplicate key)
        ERROR_SEVERITY(),     -- Severity level (0-25)
        ERROR_STATE(),        -- Error state for debugging
        ISNULL(ERROR_PROCEDURE(), 'Ad hoc query'),  -- NULL if not in a procedure
        ERROR_LINE(),         -- Line number where error occurred
        ERROR_MESSAGE()       -- Full error message text
    );

    -- Re-raise the error to the calling application
    THROW;
END CATCH;

팁 (조언)

다시 평가하기 전에 항상 오류를 기록합니다. THROW 또는 RAISERROR을 처음 사용한 후 다시 호출하면, 오류 함수가 NULL를 반환합니다.

TRY-CATCH 구조로 트랜잭션 처리하기

트랜잭션 내에서 오류가 발생하면 커밋되지 않은 작업을 명시적으로 롤백해야 합니다. 이 함수는 @@TRANCOUNT 트랜잭션이 활성 상태인지 여부를 알려줍니다.

BEGIN TRY
    -- Start a transaction to group multiple operations
    BEGIN TRANSACTION;

    -- First operation: update product prices
    UPDATE SalesLT.Product
    SET ListPrice = ListPrice * 1.05
    WHERE ProductCategoryID = 5;

    -- Second operation: update order totals
    -- If this fails, we want to undo the first update too
    UPDATE SalesLT.SalesOrderHeader
    SET TotalDue = TotalDue * 1.05
    WHERE CustomerID = 12345;

    -- Both operations succeeded, make changes permanent
    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    -- Check if a transaction is still active before rolling back
    -- Some errors auto-rollback, so @@TRANCOUNT might be 0
    IF @@TRANCOUNT > 0
        ROLLBACK TRANSACTION;  -- Undo all changes from this transaction

    -- Re-raise the error so the caller knows something failed
    THROW;
END CATCH;

@@TRANCOUNT 검사는 중요한데 그 이유는 다음과 같습니다.

  • 이전에 오류가 발생할 수 있습니다. BEGIN TRANSACTION
  • 일부 오류는 CATCH에 도달하기 전에 트랜잭션을 자동으로 롤백합니다.
  • 활성 트랜잭션 없이 롤백을 시도하면 또 다른 오류가 발생합니다.

중요합니다

항상 @@TRANCOUNT 블록에서 ROLLBACK TRANSACTION를 호출하기 전에 CATCH를 확인하십시오. 이렇게 하면 "ROLLBACK TRANSACTION 요청에 해당 BEGIN TRANSACTION이 없습니다." 오류가 발생하지 않습니다.

THROW를 사용하여 사용자 지정 오류를 발생시킵니다.

이 문장은 THROW 사용자 정의 오류 번호와 메시지를 사용하여 예외를 발생시킵니다. 이를 사용하여 애플리케이션별 오류 조건을 알릴 수 있습니다.

CREATE PROCEDURE ProcessOrder
    @OrderID INT,
    @Quantity INT
AS
BEGIN
    BEGIN TRY
        -- Validate input and raise custom errors for invalid data
        IF @Quantity <= 0
            THROW 50001, 'Quantity must be greater than zero.', 1;

        IF NOT EXISTS (SELECT 1 FROM Orders WHERE OrderID = @OrderID)
            THROW 50002, 'Order not found.', 1;

        -- Process the order
        UPDATE Orders
        SET Quantity = @Quantity
        WHERE OrderID = @OrderID;

    END TRY
    BEGIN CATCH
        -- Log the error before reraising
        EXEC LogError;

        -- THROW without parameters reraises the current error
        THROW;
    END CATCH;
END;

사용자 정의 오류에 대한 사용자 지정 오류 번호는 50000 이상이어야 합니다. 상태 매개 변수(예제의 1)는 오류가 발생한 위치를 식별하는 데 도움이 되는 1에서 255 사이의 사용자 정의 값입니다.

서식이 지정된 메시지에 사용 RAISERROR

RAISERRORTHROW 보다 printf 스타일 매개 변수 대체를 포함한 더 많은 서식 옵션을 제공합니다. 오류 메시지에 런타임 값을 포함하면 로그를 파헤치거나 문제를 재현하지 않고 오류를 일으킨 데이터를 정확하게 확인할 수 있으므로 디버깅이 더 쉬워집니다.

DECLARE @ProductName NVARCHAR(100) = 'Widget Pro';
DECLARE @CurrentStock INT = 5;
DECLARE @RequestedQty INT = 10;

IF @CurrentStock < @RequestedQty
BEGIN
    RAISERROR(
        'Insufficient stock for product "%s". Available: %d, Requested: %d',
        16,  -- Severity
        1,   -- State
        @ProductName,
        @CurrentStock,
        @RequestedQty
    );
END;

비고

THROW 는 더 간단하고 항상 스택 추적을 포함하기 때문에 새 코드에 권장되는 방법입니다. 서식이 지정된 메시지 또는 기존 오류 처리 패턴과의 호환성이 필요한 경우에 사용합니다 RAISERROR .

중첩된 오류 처리 구현

다른 프로시저를 호출하는 저장 프로시저에는 조정된 오류 처리가 필요합니다. 각 수준은 자체 정리를 처리하고 오류를 적절하게 전파해야 합니다.

CREATE PROCEDURE OuterProcedure
AS
BEGIN
    BEGIN TRY
        -- Outer procedure owns the transaction
        BEGIN TRANSACTION;

        -- First operation in the outer procedure
        UPDATE SomeTable SET Column1 = 'Value';

        -- Call nested procedure - if it fails, error propagates here
        EXEC InnerProcedure;

        -- All operations succeeded, commit the transaction
        COMMIT TRANSACTION;
    END TRY
    BEGIN CATCH
        -- Outer procedure handles rollback for all nested calls
        IF @@TRANCOUNT > 0
            ROLLBACK TRANSACTION;

        -- Propagate error to the application
        THROW;
    END CATCH;
END;
GO

CREATE PROCEDURE InnerProcedure
AS
BEGIN
    BEGIN TRY
        -- Inner procedure does its work within the outer's transaction
        UPDATE AnotherTable SET Column2 = 'Value';
    END TRY
    BEGIN CATCH
        -- Don't rollback here - let the outer procedure handle it
        -- This keeps transaction management in one place
        THROW;  -- Re-raise error to outer procedure
    END CATCH;
END;

자동 롤백에 사용 XACT_ABORT

다음과 같이 오류가 발생할 때 XACT_ABORT ON를 설정하고, TRY...CATCH 없이도 SQL Server가 트랜잭션을 자동으로 롤백하도록 할 수 있습니다.

SET XACT_ABORT ON;

BEGIN TRANSACTION;
    UPDATE Table1 SET Col1 = 'A';
    UPDATE Table2 SET Col2 = 'B';  -- If this fails, entire transaction rolls back
    UPDATE Table3 SET Col3 = 'C';
COMMIT TRANSACTION;

XACT_ABORT TRY...CATCH 이 두 방법을 XACT_ABORT 결합하면 오류에 대한 즉각적인 롤백을 보장하는 동시에 TRY...CATCH 오류 세부 정보를 기록하고 오류가 전파되기 전에 사용자 지정 정리를 수행할 수 있습니다.

-- XACT_ABORT ON ensures automatic rollback on any error
SET XACT_ABORT ON;

BEGIN TRY
    BEGIN TRANSACTION;

    -- Execute multiple procedures as a single unit of work
    EXEC Procedure1;  -- If any of these fail...
    EXEC Procedure2;  -- ...XACT_ABORT automatically rolls back...
    EXEC Procedure3;  -- ...and jumps to the CATCH block

    -- All succeeded, commit the changes
    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    -- With XACT_ABORT ON, the transaction is usually already rolled back
    -- This check handles edge cases where it might still be active
    IF @@TRANCOUNT > 0
        ROLLBACK TRANSACTION;

    -- Log the error details before re-raising
    EXEC LogError;

    -- Let the caller know an error occurred
    THROW;
END CATCH;

팁 (조언)

사용 SET XACT_ABORT ON 은 저장 프로시저, 특히 여러 작업에 걸쳐 있는 프로시저에 대한 모범 사례입니다. 발생하는 특정 오류에 관계없이 일관된 동작을 보장합니다.

오류 처리에 대한 자세한 내용은 TRY... CATCH(Transact-SQL)THROW(Transact-SQL)입니다.