PostgreSQL 42P11 오류 원인과 해결 방법 완벽 가이드

42P11
2026년 09월 16일 | DBMS Error 가이드

이 글에서 다루는 내용

42P11 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.

42P11 invalid cursor definition 는?

PostgreSQL 에러 코드 42P11은 커서(Cursor)를 정의하는 과정에서 문법적으로 잘못되었거나 허용되지 않는 방식으로 커서를 선언했을 때 발생합니다. 커서는 대용량 결과 집합을 행 단위로 처리하기 위한 데이터베이스 객체인데, 그 정의 방식이 PostgreSQL의 규칙에 맞지 않으면 이 에러가 트리거됩니다. 주로 PL/pgSQL 함수나 저장 프로시저 내부에서 커서를 선언하거나 DECLARE 구문을 사용할 때, 혹은 SQL 레벨에서 DECLARE CURSOR 문을 잘못 작성했을 때 나타납니다.


주요 발생 원인

1. WITH HOLD 또는 SCROLL 옵션을 잘못 조합한 경우

PostgreSQL 커서는 SCROLL, NO SCROLL, WITH HOLD, WITHOUT HOLD 등의 옵션을 지원합니다. 그러나 이 옵션들을 서로 충돌하는 방식으로 조합하거나, 특정 쿼리 유형에서 허용되지 않는 옵션을 사용하면 42P11 에러가 발생합니다. 예를 들어, WITH HOLD 커서는 트랜잭션 종료 후에도 유지되어야 하므로, 트랜잭션 제어와 관련된 특정 제약 조건을 위반할 경우 정의 자체가 무효화됩니다.

2. PL/pgSQL에서 커서 변수 선언 방식이 잘못된 경우

PL/pgSQL 블록 내에서 커서를 선언할 때는 정해진 문법을 정확히 따라야 합니다. 커서 변수를 CURSOR FOR 없이 선언하거나, 바인드 파라미터(bound parameter)를 잘못된 위치에 사용하거나, REFCURSOR 타입과 일반 커서 선언을 혼용하는 실수가 흔히 발생합니다. 특히 파라미터가 있는 커서(parameterized cursor)를 선언할 때 파라미터 목록의 문법 오류가 42P11을 유발하는 주된 원인 중 하나입니다.

3. 트랜잭션 외부 또는 부적절한 컨텍스트에서 커서를 선언한 경우

SQL 표준에서 커서는 반드시 활성 트랜잭션 컨텍스트 안에서 선언되어야 합니다. WITH HOLD 옵션을 사용하지 않는 일반 커서를 트랜잭션 블록 외부에서 열려고 하거나, autocommit 모드가 켜진 상태에서 잘못된 방식으로 커서를 다루면 커서 정의가 무효로 판정됩니다. 또한 함수 내부가 아닌 일반 SQL 세션에서 PL/pgSQL 전용 커서 문법을 사용하려 할 때도 이 에러가 나타납니다.


해결 방법

원인 1 해결: SCROLL/WITH HOLD 옵션 올바르게 사용하기

잘못된 예시:

-- 잘못된 예: NO SCROLL과 SCROLL을 동시에 지정하거나 충돌 옵션 사용
BEGIN;
DECLARE my_cursor NO SCROLL WITH HOLD CURSOR FOR
    SELECT id, name FROM employees;
-- 위 구문은 논리적으로 충돌할 수 있음

올바른 예시:

-- SCROLL 커서: 앞뒤 방향 이동 가능
BEGIN;
DECLARE my_scroll_cursor SCROLL CURSOR FOR
    SELECT id, name FROM employees ORDER BY id;

FETCH NEXT FROM my_scroll_cursor;
FETCH PRIOR FROM my_scroll_cursor;
CLOSE my_scroll_cursor;
COMMIT;

-- WITH HOLD 커서: 트랜잭션 종료 후에도 유지
BEGIN;
DECLARE my_hold_cursor WITH HOLD CURSOR FOR
    SELECT id, name FROM employees;

FETCH ALL FROM my_hold_cursor;
COMMIT;
-- COMMIT 이후에도 커서 사용 가능
FETCH NEXT FROM my_hold_cursor;
CLOSE my_hold_cursor;

원인 2 해결: PL/pgSQL 커서 선언 문법 교정

잘못된 예시:

-- 잘못된 예: 파라미터 커서 문법 오류
CREATE OR REPLACE FUNCTION get_employee_cursor()
RETURNS void AS $$
DECLARE
    -- 잘못된 선언: CURSOR FOR 없이 쿼리만 지정
    emp_cursor := CURSOR SELECT * FROM employees;
BEGIN
    OPEN emp_cursor;
END;
$$ LANGUAGE plpgsql;

올바른 예시:

-- 올바른 예: 파라미터 없는 커서
CREATE OR REPLACE FUNCTION process_employees()
RETURNS void AS $$
DECLARE
    emp_cursor CURSOR FOR
        SELECT id, name, salary FROM employees WHERE active = true;
    emp_record RECORD;
BEGIN
    OPEN emp_cursor;
    LOOP
        FETCH emp_cursor INTO emp_record;
        EXIT WHEN NOT FOUND;
        RAISE NOTICE 'Processing employee: % - %', emp_record.id, emp_record.name;
    END LOOP;
    CLOSE emp_cursor;
END;
$$ LANGUAGE plpgsql;

-- 올바른 예: 파라미터가 있는 커서 (parameterized cursor)
CREATE OR REPLACE FUNCTION get_employees_by_dept(p_dept_id INT)
RETURNS void AS $$
DECLARE
    -- 파라미터 커서는 이렇게 선언
    dept_cursor CURSOR (dept_id INT) FOR
        SELECT id, name, salary
        FROM employees
        WHERE department_id = dept_id;
    emp_record RECORD;
BEGIN
    OPEN dept_cursor(p_dept_id);  -- 파라미터 전달
    LOOP
        FETCH dept_cursor INTO emp_record;
        EXIT WHEN NOT FOUND;
        RAISE NOTICE 'Employee: %, Salary: %', emp_record.name, emp_record.salary;
    END LOOP;
    CLOSE dept_cursor;
END;
$$ LANGUAGE plpgsql;

원인 3 해결: 트랜잭션 컨텍스트 확인 및 REFCURSOR 활용

잘못된 예시:

-- 잘못된 예: 트랜잭션 없이 일반 커서 선언
DECLARE my_cursor CURSOR FOR SELECT * FROM employees;
-- autocommit 환경에서는 트랜잭션이 즉시 끝나 커서가 무효화될 수 있음

올바른 예시:

-- 올바른 예: 명시적 트랜잭션 블록 안에서 커서 사용
BEGIN;

DECLARE employee_cursor CURSOR FOR
    SELECT id, name, department_id, salary
    FROM employees
    WHERE salary > 50000
    ORDER BY salary DESC;

FETCH 10 FROM employee_cursor;  -- 상위 10건 조회
FETCH NEXT FROM employee_cursor;
CLOSE employee_cursor;

COMMIT;

-- REFCURSOR를 반환하는 함수 예시 (더 유연한 방식)
CREATE OR REPLACE FUNCTION open_employee_cursor(p_min_salary NUMERIC)
RETURNS refcursor AS $$
DECLARE
    ref refcursor := 'employee_ref';  -- 명명된 커서
BEGIN
    OPEN ref FOR
        SELECT id, name, salary
        FROM employees
        WHERE salary >= p_min_salary
        ORDER BY salary DESC;
    RETURN ref;
END;
$$ LANGUAGE plpgsql;

-- 호출 방법
BEGIN;
SELECT open_employee_cursor(60000);
FETCH ALL FROM employee_ref;
CLOSE employee_ref;
COMMIT;

예방 방법

1. 커서 선언 전 문법 체크리스트 운영

팀 내에서 커서를 사용하는 코드를 작성할 때는 반드시 표준 템플릿을 만들어 두고, DECLARE → OPEN → FETCH → CLOSE 4단계 사이클이 정확히 구현되었는지 코드 리뷰 체크리스트에 포함시키세요. 특히 파라미터 커서를 사용할 때는 선언부의 파라미터 목록과 OPEN 시 전달하는 인자가 타입과 순서 모두 일치하는지 반드시 확인해야 합니다. CI/CD 파이프라인에 pg_dump --schema-only\i 명령으로 함수 정의를 검증하는 단계를 추가하는 것도 좋은 방법입니다.

2. WITH HOLD 커서는 반드시 명시적으로 CLOSE 처리

WITH HOLD 커서는 트랜잭션이 종료된 후에도 서버 리소스를 점유하므로, 사용 후 반드시 CLOSE 명령으로 닫아야 합니다. 애플리케이션 레이어에서 커서를 사용하는 경우 try-finally 또는 try-with-resources 패턴을 활용해 예외 상황에서도 커서가 반드시 닫히도록 구현하세요. PostgreSQL 세션에서 열려 있는 커서 목록은 pg_cursors 시스템 뷰를 통해 모니터링할 수 있으며, 이를 정기적으로 점검하는 모니터링 쿼리를 운영 환경에 추가하는 것을 권장합니다.

-- 열려 있는 커서 목록 모니터링
SELECT name, statement, is_holdable, is_scrollable, creation_time
FROM pg_cursors
ORDER BY creation_time;

관련 에러

  • 34000 (invalid cursor name): 존재하지 않는 커서 이름을 FETCH, CLOSE, MOVE 명령에서 참조할 때 발생합니다. 42P11이 커서 정의 단계의 문제라면, 34000은 커서 사용 단계의 문제입니다.
  • 24000 (invalid transaction state): 트랜잭션 상태가 커서 조작에 부적합할 때 발생하며, WITH HOLD 없이 커서를 트랜잭션 외부에서 사용하려 할 때 함께 나타날 수 있습니다.
  • 42601 (syntax error): 커서 선언 문법이 완전히 잘못된 경우 42P11 대신 일반 문법 오류로 처리될 수 있습니다. 두 에러가 비슷한 상황에서 발생하므로 함께 확인하세요.
DBMS 에러 코드 시리즈

주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.

본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.

댓글 남기기