2026년 10월 10일 | DBMS Error 가이드
이 글에서 다루는 내용
0Z000 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.
0Z000 diagnostics exception 는?
PostgreSQL에서 0Z000 에러 코드는 diagnostics exception을 의미하며, PL/pgSQL 또는 다른 절차적 언어 블록 내에서 GET DIAGNOSTICS 구문이나 진단 관련 처리 중 예외가 발생했을 때 나타납니다. 이 에러는 주로 저장 프로시저(Stored Procedure), 함수(Function), 트리거(Trigger) 내부에서 실행 흐름 제어나 진단 정보를 수집하는 과정에서 발생하며, 잘못된 진단 변수 참조나 컨텍스트 외부 호출이 원인인 경우가 많습니다. SQL 표준의 진단 예외 클래스(Class 0Z)에 속하며, 데이터베이스 진단 메커니즘 자체에 관련된 오류를 포괄합니다.
주요 발생 원인
1. GET DIAGNOSTICS 구문의 잘못된 사용
GET DIAGNOSTICS 또는 GET STACKED DIAGNOSTICS 구문은 반드시 적절한 컨텍스트 내에서 호출되어야 합니다. 특히 GET STACKED DIAGNOSTICS는 반드시 EXCEPTION 블록 내부에서만 사용 가능하며, 이를 벗어난 위치에서 호출하면 진단 예외가 발생합니다. 많은 개발자들이 일반 실행 블록에서 스택 진단 정보를 가져오려 시도하다가 이 에러를 마주칩니다.
2. 잘못된 진단 항목(Diagnostic Item) 참조
GET DIAGNOSTICS에서 사용할 수 있는 진단 항목은 PostgreSQL 버전에 따라 제한되어 있으며, 지원되지 않는 항목을 참조하거나 오타가 있는 진단 항목명을 사용할 경우 이 에러가 트리거됩니다. 예를 들어 ROW_COUNT, RESULT_OID, PG_CONTEXT 등 정해진 항목 외의 임의 변수명을 사용하면 파서 또는 런타임 레벨에서 예외가 발생합니다. 진단 항목 이름은 대소문자를 구분하지 않지만 정확한 명칭을 사용해야 합니다.
3. 중첩된 예외 블록에서의 진단 컨텍스트 손실
PL/pgSQL에서 중첩된 BEGIN...EXCEPTION...END 블록을 사용할 때, 내부 블록의 예외를 처리하고 외부 블록에서 다시 GET STACKED DIAGNOSTICS를 호출하려 할 경우 진단 컨텍스트가 이미 소멸된 상태일 수 있습니다. 이 경우 진단 정보가 유효하지 않은 상태에서 접근이 시도되어 0Z000 에러가 발생할 수 있습니다. 중첩 예외 구조의 설계 시 각 레벨에서의 진단 정보 수명(lifetime)을 명확히 이해하는 것이 중요합니다.
해결 방법
원인 1 해결: GET STACKED DIAGNOSTICS는 반드시 EXCEPTION 블록 내에서 사용
-- 잘못된 예 (일반 블록에서 STACKED DIAGNOSTICS 호출)
DO $$
DECLARE
v_state TEXT;
v_message TEXT;
BEGIN
-- 여기서는 GET STACKED DIAGNOSTICS 사용 불가
GET STACKED DIAGNOSTICS v_state = RETURNED_SQLSTATE,
v_message = MESSAGE_TEXT;
RAISE NOTICE 'State: %', v_state;
END;
$$;
-- 올바른 예 (EXCEPTION 블록 내부에서 사용)
DO $$
DECLARE
v_state TEXT;
v_message TEXT;
v_context TEXT;
BEGIN
-- 의도적으로 에러를 발생시키는 작업
PERFORM 1 / 0;
EXCEPTION
WHEN OTHERS THEN
-- EXCEPTION 블록 내에서만 GET STACKED DIAGNOSTICS 사용 가능
GET STACKED DIAGNOSTICS
v_state = RETURNED_SQLSTATE,
v_message = MESSAGE_TEXT,
v_context = PG_EXCEPTION_CONTEXT;
RAISE NOTICE '에러 코드: %', v_state;
RAISE NOTICE '에러 메시지: %', v_message;
RAISE NOTICE '에러 컨텍스트: %', v_context;
END;
$$;
원인 2 해결: 올바른 진단 항목명 사용
-- 잘못된 예 (존재하지 않는 진단 항목 사용)
DO $$
DECLARE
v_rows INTEGER;
BEGIN
UPDATE employees SET salary = salary * 1.1 WHERE department_id = 10;
-- 잘못된 항목명 사용 예시 (AFFECTED_ROWS는 존재하지 않음)
-- GET DIAGNOSTICS v_rows = AFFECTED_ROWS; -- 에러 발생!
-- 올바른 항목명: ROW_COUNT
GET DIAGNOSTICS v_rows = ROW_COUNT;
RAISE NOTICE '업데이트된 행 수: %', v_rows;
END;
$$;
-- 실무에서 자주 사용하는 올바른 진단 항목 예시
DO $$
DECLARE
v_row_count INTEGER;
v_result_oid OID;
BEGIN
INSERT INTO audit_log (action, created_at)
VALUES ('DATA_SYNC', NOW());
-- 올바른 진단 항목 사용
GET DIAGNOSTICS
v_row_count = ROW_COUNT, -- 영향받은 행 수
v_result_oid = RESULT_OID; -- 마지막 삽입 OID (레거시)
RAISE NOTICE '삽입 행 수: %, OID: %', v_row_count, v_result_oid;
END;
$$;
원인 3 해결: 중첩 예외 블록에서 진단 정보 즉시 저장
-- 중첩 예외 블록에서 안전하게 진단 정보 처리하는 패턴
CREATE OR REPLACE FUNCTION safe_data_migration()
RETURNS VOID AS $$
DECLARE
v_state TEXT;
v_message TEXT;
v_context TEXT;
v_hint TEXT;
BEGIN
-- 외부 블록 작업
BEGIN
-- 내부 작업 시도
INSERT INTO target_table SELECT * FROM source_table;
EXCEPTION
WHEN OTHERS THEN
-- 즉시 진단 정보를 로컬 변수에 저장 (컨텍스트 소멸 전에)
GET STACKED DIAGNOSTICS
v_state = RETURNED_SQLSTATE,
v_message = MESSAGE_TEXT,
v_context = PG_EXCEPTION_CONTEXT,
v_hint = PG_EXCEPTION_HINT;
-- 진단 정보를 에러 로그 테이블에 기록
INSERT INTO error_log (error_code, error_message, error_context, occurred_at)
VALUES (v_state, v_message, v_context, NOW());
-- 필요에 따라 재발생(re-raise) 또는 복구 로직 수행
RAISE WARNING '내부 블록 에러 처리 완료 - 코드: %, 메시지: %', v_state, v_message;
END;
-- 외부 블록 계속 진행 (내부 에러가 처리되었으므로)
RAISE NOTICE '마이그레이션 완료 (일부 에러 무시됨)';
EXCEPTION
WHEN OTHERS THEN
-- 외부 블록의 별도 예외 처리
GET STACKED DIAGNOSTICS
v_state = RETURNED_SQLSTATE,
v_message = MESSAGE_TEXT;
RAISE EXCEPTION '치명적 에러 발생 [%]: %', v_state, v_message;
END;
$$ LANGUAGE plpgsql;
예방 방법
1. 표준화된 예외 처리 템플릿 사용
팀 전체가 공유하는 PL/pgSQL 예외 처리 표준 템플릿을 정의하고, 모든 저장 프로시저와 함수에 일관되게 적용하는 것이 중요합니다. 아래와 같은 공통 에러 로깅 함수를 만들어두면 GET STACKED DIAGNOSTICS의 올바른 사용 패턴을 강제할 수 있고, 진단 예외 발생 가능성을 크게 줄일 수 있습니다.
-- 공통 에러 로깅 함수 (표준 템플릿)
CREATE OR REPLACE FUNCTION log_exception(
p_proc_name TEXT,
p_state TEXT,
p_message TEXT,
p_context TEXT
) RETURNS VOID AS $$
BEGIN
INSERT INTO system_error_log (
procedure_name,
sqlstate,
error_message,
error_context,
logged_at
) VALUES (
p_proc_name,
p_state,
p_message,
p_context,
CURRENT_TIMESTAMP
);
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- 표준 템플릿 적용 예시
CREATE OR REPLACE FUNCTION process_orders(p_batch_id INTEGER)
RETURNS INTEGER AS $$
DECLARE
v_state TEXT;
v_message TEXT;
v_context TEXT;
v_count INTEGER := 0;
BEGIN
-- 비즈니스 로직
UPDATE orders SET status = 'PROCESSED' WHERE batch_id = p_batch_id;
GET DIAGNOSTICS v_count = ROW_COUNT;
RETURN v_count;
EXCEPTION
WHEN OTHERS THEN
GET STACKED DIAGNOSTICS
v_state = RETURNED_SQLSTATE,
v_message = MESSAGE_TEXT,
v_context = PG_EXCEPTION_CONTEXT;
PERFORM log_exception('process_orders', v_state, v_message, v_context);
RAISE; -- 원래 예외를 다시 발생시켜 호출자에게 전달
END;
$$ LANGUAGE plpgsql;
2. 코드 리뷰 및 정적 분석 도구 활용
GET DIAGNOSTICS와 GET STACKED DIAGNOSTICS의 사용 위치를 코드 리뷰 체크리스트에 필수 항목으로 포함시키고, plpgsql_check 확장 모듈을 활용하여 배포 전에 정적 분석을 수행하는 것을 권장합니다. plpgsql_check는 PL/pgSQL 코드의 잠재적 오류를 컴파일 타임에 잡아낼 수 있어 운영 환경에서의 0Z000 에러 발생을 사전에 차단할 수 있습니다.
-- plpgsql_check 확장 설치 및 활용
CREATE EXTENSION IF NOT EXISTS plpgsql_check;
-- 특정 함수에 대한 정적 검사 실행
SELECT * FROM plpgsql_check_function('process_orders(integer)');
-- 데이터베이스 내 모든 PL/pgSQL 함수 일괄 검사
SELECT
p.oid::regprocedure AS function_name,
e.lineno,
e.statement,
e.sqlstate,
e.message
FROM pg_proc p,
LATERAL plpgsql_check_function_tb(p.oid) e
WHERE p.prolang = (SELECT oid FROM pg_language WHERE lanname = 'plpgsql')
ORDER BY p.oid, e.lineno;
관련 에러
P0000(plpgsql_error): PL/pgSQL 일반 오류로, 진단 예외와 함께 자주 발생하는 관련 에러입니다.P0001(raise_exception):RAISE EXCEPTION구문으로 명시적으로 발생시킨 예외이며,GET STACKED DIAGNOSTICS로 정보를 수집할 수 있습니다.39000(external_routine_exception): 외부 루틴 호출 중 발생하는 예외로, PL/Python, PL/Perl 등 외부 언어 프로시저에서 진단 관련 오류가 발생할 때 연관됩니다.2F000(sql_routine_exception): SQL 함수 실행 중 발생하는 예외 클래스로, 진단 컨텍스트 처리와 관련하여 함께 검토해야 할 에러입니다.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.