DEV Community

umzzil nng
umzzil nng

Posted on • Originally published at oraerror.com

PostgreSQL 0100C 오류 원인과 해결 방법 완벽 가이드

0100C란?

PostgreSQL 에러 코드 0100C"dynamic result sets returned" 로, SQL 함수나 프로시저가 호출자가 기대하지 않은 동적 결과 셋(result set)을 반환할 때 발생하는 경고(Warning) 수준의 알림입니다. 이 코드는 주로 CALL 구문으로 호출된 프로시저(Stored Procedure)가 커서(cursor)를 통해 결과를 반환하려 할 때, 호출 컨텍스트에서 이를 처리할 준비가 되어 있지 않은 경우에 나타납니다. 엄밀히 말하면 에러(ERROR)가 아닌 경고(WARNING) 클래스에 속하지만, 특정 클라이언트나 드라이버 환경에서는 이를 예외(exception)로 처리하여 쿼리 실행을 중단시키기도 하므로 실무에서 반드시 인지하고 있어야 합니다.


주요 발생 원인

1. 프로시저에서 REFCURSOR를 OUT 파라미터로 반환하는 경우

PostgreSQL의 프로시저(CREATE PROCEDURE)는 함수(CREATE FUNCTION)와 달리 RETURN 구문으로 값을 직접 반환할 수 없고, 대신 OUT 파라미터 또는 INOUT 파라미터를 통해 결과를 전달합니다. 이때 refcursor 타입을 OUT 파라미터로 선언하고 열린 커서를 반환하면, 호출 측에서 이를 명시적으로 FETCH하지 않는 한 0100C 경고가 발생합니다. 특히 JDBC, psycopg2 등 일부 드라이버는 이 경고를 예외로 격상시켜 애플리케이션 오류를 유발합니다.

2. 함수 내부에서 암묵적으로 열린 커서가 닫히지 않은 경우

PL/pgSQL 블록 내에서 OPEN cursor_name FOR SELECT ... 구문으로 커서를 열고 명시적으로 CLOSE하지 않으면, 트랜잭션이 끝날 때까지 해당 커서가 열린 상태로 남게 됩니다. 이 경우 PostgreSQL은 결과 셋이 여전히 소비되지 않았다고 판단하여 0100C를 발생시킵니다. 특히 예외 처리 블록(EXCEPTION WHEN ...)에서 조기 종료되는 경우에 이런 상황이 빈번히 발생합니다.

3. 애플리케이션 레이어에서 CALL 구문 후 결과 셋을 소비하지 않는 경우

애플리케이션에서 CALL procedure_name(...) 을 실행한 뒤 반환된 커서를 FETCH ALL FROM cursor_name 등으로 소비하지 않고 트랜잭션을 커밋하거나 롤백하는 경우에도 이 경고가 발생합니다. 일부 ORM(예: SQLAlchemy, Hibernate)은 프로시저 호출 시 결과 셋 처리 로직이 자동화되어 있지 않아, 개발자가 직접 커서 소비 코드를 작성해야 합니다.


해결 방법

원인 1 해결: REFCURSOR를 올바르게 사용하고 호출 측에서 명시적으로 FETCH 처리

프로시저를 정의할 때 refcursorOUT 파라미터로 선언하고, 호출 측에서 반드시 해당 커서를 FETCH한 뒤 CLOSE로 닫아줍니다.

-- 프로시저 정의
CREATE OR REPLACE PROCEDURE get_active_users(OUT p_cursor refcursor)
LANGUAGE plpgsql
AS $$
BEGIN
    OPEN p_cursor FOR
        SELECT user_id, username, email
        FROM users
        WHERE is_active = TRUE
        ORDER BY created_at DESC;
END;
$$;

-- 올바른 호출 방법 (반드시 트랜잭션 내에서 수행)
BEGIN;

-- 프로시저 호출: OUT 파라미터로 커서 이름 지정
CALL get_active_users('user_cursor');

-- 커서에서 결과 소비
FETCH ALL FROM user_cursor;

-- 커서 명시적 종료
CLOSE user_cursor;

COMMIT;
Enter fullscreen mode Exit fullscreen mode

위 예시처럼 BEGIN ~ COMMIT 트랜잭션 블록 안에서 커서를 열고, 반드시 FETCHCLOSE를 순서대로 실행해야 0100C 경고를 방지할 수 있습니다.


원인 2 해결: PL/pgSQL 블록 내 커서를 예외 처리 포함하여 안전하게 닫기

예외 처리 블록에서도 커서가 반드시 닫히도록 구조를 설계합니다.

CREATE OR REPLACE PROCEDURE process_orders(IN p_status TEXT)
LANGUAGE plpgsql
AS $$
DECLARE
    v_cursor CURSOR FOR
        SELECT order_id, amount FROM orders WHERE status = p_status;
    v_order_id  orders.order_id%TYPE;
    v_amount    orders.amount%TYPE;
BEGIN
    OPEN v_cursor;

    LOOP
        FETCH v_cursor INTO v_order_id, v_amount;
        EXIT WHEN NOT FOUND;

        -- 주문 처리 로직
        UPDATE orders
        SET processed_at = NOW()
        WHERE order_id = v_order_id;

    END LOOP;

    -- 정상 종료 시 커서 닫기
    CLOSE v_cursor;

EXCEPTION
    WHEN OTHERS THEN
        -- 예외 발생 시에도 반드시 커서를 닫아야 0100C 방지
        IF v_cursor%ISOPEN THEN
            CLOSE v_cursor;
        END IF;
        RAISE WARNING '주문 처리 중 오류 발생: %', SQLERRM;
        RAISE;
END;
$$;
Enter fullscreen mode Exit fullscreen mode

%ISOPEN 속성을 사용하여 커서가 열려 있는지 확인한 뒤 닫는 패턴은, 예외 상황에서도 안전하게 리소스를 정리하는 PostgreSQL 실무의 기본 관행입니다.


원인 3 해결: 함수로 전환하거나 애플리케이션에서 커서 소비 코드 명시

결과 셋을 단순히 반환하는 용도라면, 프로시저 대신 SETOF 또는 TABLE()을 반환하는 함수로 전환하는 것이 더 명확합니다.

-- PROCEDURE 대신 FUNCTION으로 전환하여 0100C 원천 방지
CREATE OR REPLACE FUNCTION get_active_users_fn()
RETURNS TABLE(user_id INT, username TEXT, email TEXT)
LANGUAGE plpgsql
AS $$
BEGIN
    RETURN QUERY
        SELECT u.user_id, u.username, u.email
        FROM users u
        WHERE u.is_active = TRUE
        ORDER BY u.created_at DESC;
END;
$$;

-- 함수 호출 (커서 불필요, 직접 SELECT로 소비)
SELECT * FROM get_active_users_fn();
Enter fullscreen mode Exit fullscreen mode
-- Python psycopg2에서 refcursor를 명시적으로 처리하는 예시
-- (주석으로 설명)
-- conn = psycopg2.connect(...)
-- conn.autocommit = False  -- 트랜잭션 필수
-- cur = conn.cursor()
-- cur.callproc('get_active_users', ['my_cursor'])
-- cur.execute('FETCH ALL FROM my_cursor')
-- rows = cur.fetchall()
-- cur.execute('CLOSE my_cursor')
-- conn.commit()

-- 위 Python 흐름을 순수 SQL로 표현하면:
BEGIN;
CALL get_active_users('my_cursor');
FETCH ALL FROM my_cursor;
CLOSE my_cursor;
COMMIT;
Enter fullscreen mode Exit fullscreen mode

예방 방법

1. 결과 반환이 목적이라면 PROCEDURE보다 FUNCTION을 우선 선택하라

PostgreSQL에서 데이터를 조회하여 반환하는 목적이라면, PROCEDURE보다 RETURNS TABLE 또는 RETURNS SETOF를 사용하는 FUNCTION이 훨씬 명확하고 안전합니다. FUNCTIONSELECT 문으로 직접 호출할 수 있어 커서 관리가 불필요하며, 0100C가 원천적으로 발생하지 않습니다. PROCEDURE는 트랜잭션 제어(COMMIT, ROLLBACK)가 필요한 경우에만 사용하는 것을 팀 내 코딩 컨벤션으로 정착시키는 것이 좋습니다.

2. 커서를 사용하는 모든 프로시저에 반드시 EXCEPTION 블록과 커서 종료 로직을 포함하라

커서를 사용하는 PL/pgSQL 코드를 작성할 때는 코드 리뷰 체크리스트에 "EXCEPTION 블록 내 CLOSE 처리 여부"를 반드시 포함시키세요. CI/CD 파이프라인에서 pgTAP을 활용한 자동화 테스트를 구성하면, 커서 미종료 문제를 배포 전에 탐지할 수 있습니다. 또한 log_min_messages = WARNING 설정을 통해 0100C 경고를 PostgreSQL 로그에 남겨 모니터링 알림과 연동하는 것도 좋은 관행입니다.


관련 에러

에러 코드 이름 관계
01000 warning 0100C의 상위 경고 클래스로, 일반적인 경고 상황 전반을 포괄
01P01 deprecated_feature 구식 기능 사용 시 발생하는 경고로, 커서 관련 구식 API 사용 시 함께 발생 가능
24000 invalid_cursor_state 이미 닫힌 커서를 FETCH하거나 열리지 않은 커서를 참조할 때 발생하며, 0100C 처리 누락 후 후속으로 발생하는 경우가 많음
34000 invalid_cursor_name 존재하지 않는 커서 이름으로 FETCHCLOSE를 시도할 때 발생하며, 커서 이름 관리 실수와 연관
40001 serialization_failure 트랜잭션 내 커서 처리 중 직렬화 충돌이 발생할 경우 0100C와 함께 복합적으로 나타날 수 있음

0100C는 경고 코드이지만, 실무에서는 커서 리소스 누수와 드라이버 레벨의 예외 처리 문제로 이어질 수 있으므로 에러와 동일한 수준으로 취급하여 관리하는 것을 강력히 권장합니다.

Top comments (0)