2026년 06월 02일 | DBMS Error 가이드
이 글에서 다루는 내용
0Z000 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.
0Z000 diagnostics exception 는?
PostgreSQL 에러 코드 0Z000은 diagnostics exception으로, PL/pgSQL 또는 다른 절차적 언어(PL/Python, PL/Perl 등) 내부에서 진단(diagnostics) 관련 처리 중 예외가 발생했을 때 나타나는 에러입니다. 이 에러는 주로 GET DIAGNOSTICS 구문이나 예외 처리 블록(EXCEPTION 블록) 내부에서 진단 정보를 가져오거나 처리하는 과정에서 잘못된 사용이 있을 때 발생합니다. 실무에서는 복잡한 저장 프로시저(Stored Procedure)나 함수(Function) 안에서 예외 핸들링 로직이 잘못 구성되었을 때 자주 마주치는 에러입니다.
주요 발생 원인
GET DIAGNOSTICS구문의 잘못된 사용
GET DIAGNOSTICS는 PL/pgSQL 함수 내에서 마지막으로 실행된 SQL 명령의 영향을 받은 행 수(ROW_COUNT) 또는 스택 정보(PG_CONTEXT) 등을 가져오는 데 사용됩니다. 그러나 이 구문을 EXCEPTION 블록 밖에서 예외 관련 변수(RETURNED_SQLSTATE, MESSAGE_TEXT, PG_EXCEPTION_DETAIL 등)를 조회하려 하거나, 반대로 EXCEPTION 블록 내부에서 일반 DML 전용 변수를 사용하는 경우 0Z000 에러가 발생할 수 있습니다. 각 진단 변수는 사용 가능한 컨텍스트가 명확히 구분되어 있으므로 반드시 공식 문서를 참고하여 올바른 위치에서 사용해야 합니다.
EXCEPTION블록에서의 잘못된 예외 처리 로직
PL/pgSQL의 BEGIN...EXCEPTION...END 블록에서 예외를 잡은 후, 예외 정보를 다시 다른 서브블록으로 전달하거나 중첩된 예외 처리 구조를 잘못 구성하면 진단 예외가 발생할 수 있습니다. 예를 들어, 내부 블록에서 발생한 예외를 외부 블록에서 처리하면서 GET STACKED DIAGNOSTICS를 잘못 호출하거나, 이미 소비된 예외 정보를 재참조하는 경우 문제가 생깁니다. 이러한 상황은 특히 대규모 ERP 시스템이나 복잡한 비즈니스 로직이 담긴 패키지형 함수에서 자주 발생합니다.
- PL/pgSQL 이외의 절차적 언어(PL/Python, PL/Perl 등)에서의 진단 처리 오류
PostgreSQL은 PL/pgSQL 외에도 PL/Python, PL/Perl, PL/Tcl 등 다양한 절차적 언어를 지원합니다. 이러한 외부 언어 확장에서 PostgreSQL 내부 진단 API를 잘못 호출하거나, 해당 언어의 예외 객체를 PostgreSQL 에러 코드 체계와 매핑하는 과정에서 오류가 발생하면 0Z000 에러로 나타날 수 있습니다. 특히 PL/Python에서 Python 예외를 PostgreSQL 예외로 변환하는 로직이 불완전하거나, 예상치 못한 Python 예외 타입이 발생했을 때 이 에러를 마주치는 경우가 있습니다.
해결 방법
원인 1 해결: GET DIAGNOSTICS 올바른 사용법
EXCEPTION 블록 내부와 외부에서 사용할 수 있는 진단 변수가 다르다는 점을 반드시 기억하세요. 아래 예제는 올바른 사용법을 보여줍니다.
-- 올바른 GET DIAGNOSTICS 사용 예제
CREATE OR REPLACE FUNCTION correct_diagnostics_example()
RETURNS void AS $$
DECLARE
v_row_count INTEGER;
v_sqlstate TEXT;
v_message TEXT;
v_detail TEXT;
v_context TEXT;
BEGIN
-- 일반 DML 실행 후 ROW_COUNT 사용 (EXCEPTION 블록 밖)
UPDATE employees SET salary = salary * 1.1 WHERE department = 'IT';
GET DIAGNOSTICS v_row_count = ROW_COUNT;
RAISE NOTICE '업데이트된 행 수: %', v_row_count;
EXCEPTION
WHEN OTHERS THEN
-- EXCEPTION 블록 안에서는 GET STACKED DIAGNOSTICS 사용
GET STACKED DIAGNOSTICS
v_sqlstate = RETURNED_SQLSTATE,
v_message = MESSAGE_TEXT,
v_detail = PG_EXCEPTION_DETAIL,
v_context = PG_EXCEPTION_CONTEXT;
RAISE WARNING '에러 코드: %, 메시지: %, 상세: %',
v_sqlstate, v_message, v_detail;
-- 에러 로그 테이블에 기록
INSERT INTO error_log(error_code, error_message, error_detail, logged_at)
VALUES (v_sqlstate, v_message, v_detail, NOW());
END;
$$ LANGUAGE plpgsql;
> 핵심 포인트: 일반 컨텍스트에서는 GET DIAGNOSTICS를, 예외 처리 블록 내부에서는 반드시 GET STACKED DIAGNOSTICS를 사용해야 합니다.
원인 2 해결: 중첩 예외 처리 구조 개선
-- 잘못된 중첩 예외 처리 (문제 발생 가능)
-- 아래처럼 내부 블록 예외를 외부에서 잘못 처리하는 패턴
CREATE OR REPLACE FUNCTION bad_nested_exception()
RETURNS void AS $$
DECLARE
v_sqlstate TEXT;
v_message TEXT;
BEGIN
BEGIN
-- 내부 블록
INSERT INTO orders(order_id, amount) VALUES (NULL, 100);
EXCEPTION
WHEN not_null_violation THEN
-- 여기서 예외를 잡았으므로 외부 블록으로 전파되지 않음
RAISE NOTICE '내부 블록에서 예외 처리됨';
-- GET STACKED DIAGNOSTICS는 이 블록 안에서만 유효
GET STACKED DIAGNOSTICS
v_sqlstate = RETURNED_SQLSTATE,
v_message = MESSAGE_TEXT;
RAISE NOTICE 'SQLSTATE: %, Message: %', v_sqlstate, v_message;
END;
EXCEPTION
WHEN OTHERS THEN
-- 올바른 방법: 외부 블록의 EXCEPTION에서는 외부 블록의 진단 정보만 참조
GET STACKED DIAGNOSTICS
v_sqlstate = RETURNED_SQLSTATE,
v_message = MESSAGE_TEXT;
RAISE WARNING '외부 블록 에러: %, %', v_sqlstate, v_message;
END;
$$ LANGUAGE plpgsql;
-- 올바른 중첩 예외 처리 패턴
CREATE OR REPLACE FUNCTION good_nested_exception()
RETURNS void AS $$
DECLARE
v_sqlstate TEXT;
v_message TEXT;
v_context TEXT;
BEGIN
BEGIN
INSERT INTO orders(order_id, amount) VALUES (NULL, 100);
EXCEPTION
WHEN OTHERS THEN
GET STACKED DIAGNOSTICS
v_sqlstate = RETURNED_SQLSTATE,
v_message = MESSAGE_TEXT,
v_context = PG_EXCEPTION_CONTEXT;
-- 예외 정보를 변수에 저장한 후, 필요하면 재발생(RAISE)
RAISE NOTICE '처리된 에러 - 코드: %, 메시지: %', v_sqlstate, v_message;
-- 필요시 예외를 재발생시킴
-- RAISE EXCEPTION USING ERRCODE = v_sqlstate, MESSAGE = v_message;
END;
RAISE NOTICE '함수 정상 완료';
END;
$$ LANGUAGE plpgsql;
원인 3 해결: PL/Python에서의 안전한 예외 처리
-- PL/Python에서 안전하게 진단 예외 처리하기
CREATE OR REPLACE FUNCTION safe_plpython_function(p_input TEXT)
RETURNS TEXT AS $$
import traceback
try:
# 비즈니스 로직 실행
if not p_input:
raise ValueError("입력값이 비어 있습니다.")
result = p_input.upper()
return result
except ValueError as e:
# Python 예외를 PostgreSQL 예외로 명시적으로 변환
plpy.error(f"입력값 오류: {str(e)}")
except Exception as e:
# 예상치 못한 예외는 상세 스택트레이스와 함께 로깅
tb = traceback.format_exc()
plpy.warning(f"예상치 못한 오류 발생: {str(e)}\n{tb}")
plpy.error("내부 처리 오류가 발생했습니다. 관리자에게 문의하세요.")
$$ LANGUAGE plpython3u;
-- 테스트
SELECT safe_plpython_function('hello world');
SELECT safe_plpython_function(NULL);
예방 방법
- 표준화된 예외 처리 템플릿 사용
모든 PL/pgSQL 함수와 프로시저에서 아래와 같은 표준 템플릿을 사용하여 진단 정보를 일관되게 처리하고, 에러 로그 테이블에 기록하는 패턴을 조직 전체의 코딩 표준으로 채택하세요. 이를 통해 GET DIAGNOSTICS와 GET STACKED DIAGNOSTICS의 혼용을 방지하고, 예외 처리 로직의 일관성을 보장할 수 있습니다.
-- 조직 표준 예외 처리 템플릿
CREATE OR REPLACE FUNCTION template_with_error_handling(
p_param1 TEXT,
p_param2 INTEGER
)
RETURNS JSONB AS $$
DECLARE
v_result JSONB;
v_row_count INTEGER;
v_sqlstate TEXT;
v_message TEXT;
v_detail TEXT;
v_hint TEXT;
v_context TEXT;
BEGIN
-- 메인 로직
UPDATE some_table
SET column1 = p_param1
WHERE id = p_param2;
GET DIAGNOSTICS v_row_count = ROW_COUNT;
IF v_row_count = 0 THEN
RAISE EXCEPTION 'ID %에 해당하는 레코드를 찾을 수 없습니다.', p_param2
USING ERRCODE = 'P0002'; -- no_data_found
END IF;
v_result := jsonb_build_object(
'success', true,
'rows_affected', v_row_count
);
RETURN v_result;
EXCEPTION
WHEN OTHERS THEN
GET STACKED DIAGNOSTICS
v_sqlstate = RETURNED_SQLSTATE,
v_message = MESSAGE_TEXT,
v_detail = PG_EXCEPTION_DETAIL,
v_hint = PG_EXCEPTION_HINT,
v_context = PG_EXCEPTION_CONTEXT;
-- 중앙 에러 로그 테이블에 기록
INSERT INTO app_error_log (
func_name, sqlstate, message,
detail, hint, context, logged_at
) VALUES (
'template_with_error_handling',
v_sqlstate, v_message,
v_detail, v_hint, v_context,
clock_timestamp()
);
-- 에러를 호출자에게 재전파
RAISE EXCEPTION '처리 중 오류 발생: %', v_message
USING ERRCODE = v_sqlstate,
DETAIL = v_detail,
HINT = v_hint;
END;
$$ LANGUAGE plpgsql;
- 정기적인 코드 리뷰 및 정적 분석 도구 활용
pgTAP을 사용한 단위 테스트, plpgsql_check 확장을 통한 정적 분석을 CI/CD 파이프라인에 통합하여 배포 전에 잠재적인 진단 예외 문제를 미리 탐지하세요. plpgsql_check는 PL/pgSQL 코드의 문법 오류, 타입 불일치, 잘못된 변수 참조 등을 컴파일 타임에 검사해주므로 0Z000과 같은 런타임 예외를 사전에 방지하는 데 매우 효과적입니다.
-- plpgsql_check 확장 설치 및 사용
CREATE EXTENSION IF NOT EXISTS plpgsql_check;
-- 특정 함수의 정적 분석 실행
SELECT * FROM plpgsql_check_function('template_with_error_handling(text, integer)');
-- 모든 PL/pgSQL 함수 일괄 검사
SELECT
p.proname AS function_name,
ce.message,
ce.detail,
ce.context
FROM pg_proc p
JOIN pg_namespace n ON n.oid = p.pronamespace
CROSS JOIN LATERAL plpgsql_check_function(p.oid) AS ce
WHERE n.nspname = 'public'
AND p.prolang = (SELECT oid FROM pg_language WHERE lanname = 'plpgsql')
ORDER BY p.proname;
관련 에러
P0000(plpgsql_error): PL/pgSQL 전반적인 런타임 오류로,0Z000과 함께 자주 발생합니다.P0001(raise_exception):RAISE EXCEPTION구문으로 명시적으로 발생시킨 예외로, 진단 처리 중 재발생(re-raise)할 때 연관됩니다.P0002(no_data_found): 데이터 미존재 예외로, 예외 처리 로직 내에서 진단 정보를 잘못 참조할 때0Z000과 함께 나타날 수 있습니다.P0003(too_many_rows): 예상보다 많은 행이 반환될 때 발생하며, 복잡한 예외 처리 체인에서0Z000을 유발할 수 있습니다.XX000(internal_error): PostgreSQL 내부 오류로, PL/Python 등 외부 언어에서 진단 API를 잘못 사용할 경우0Z000과 함께 로그에 남을 수 있습니다.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.