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
RAISERROR은 THROW 보다 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)입니다.