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

34000
2026년 06월 28일 | DBMS Error 가이드

이 글에서 다루는 내용

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

34000 invalid cursor name 는?

PostgreSQL 에러 코드 34000은 invalid cursor name으로, 존재하지 않거나 현재 세션에서 선언되지 않은 커서 이름을 참조하려 할 때 발생합니다. 커서(Cursor)는 SQL 쿼리의 결과 집합을 행 단위로 처리하기 위해 사용되는 데이터베이스 객체인데, 선언(DECLARE) 없이 사용하거나 이미 닫힌(CLOSE) 커서를 다시 사용하려 할 때 이 에러가 발생합니다. 특히 PL/pgSQL 프로시저나 복잡한 트랜잭션 처리 로직에서 자주 마주칠 수 있는 에러입니다.


주요 발생 원인

1. 선언되지 않은 커서 이름 참조

가장 흔한 원인으로, DECLARE 문 없이 FETCH, MOVE, CLOSE 명령어에서 커서 이름을 사용하는 경우입니다. 오타나 커서 이름의 대소문자 불일치로 인해 PostgreSQL이 해당 커서를 찾지 못하는 상황도 포함됩니다. PostgreSQL의 커서는 세션(트랜잭션) 범위 내에서만 유효하며, 이름은 식별자(identifier) 규칙을 따릅니다.

2. 트랜잭션 경계를 벗어난 커서 접근

PostgreSQL에서 커서는 기본적으로 트랜잭션 내에서만 유효합니다. COMMIT 또는 ROLLBACK 이후에도 커서를 계속 사용하려 하면, 해당 커서는 이미 무효화되어 34000 에러가 발생합니다. WITH HOLD 옵션을 명시하지 않은 커서는 트랜잭션 종료 시 자동으로 닫히기 때문에, 트랜잭션 외부에서 커서를 접근하려는 시도는 항상 이 에러를 유발합니다.

3. 이미 닫힌(CLOSE) 커서 재사용

CLOSE cursor_name 명령어로 커서를 명시적으로 닫은 뒤, 같은 이름의 커서를 다시 FETCH하거나 MOVE하려 할 때 발생합니다. 특히 반복문(loop)이나 예외 처리(exception handling) 로직에서 커서를 닫은 후 실수로 재참조하는 경우가 많습니다. 커서를 닫은 뒤 재사용하려면 반드시 다시 DECLARE하거나 OPEN해야 합니다.


해결 방법

원인 1 해결: 커서 선언 확인 및 수정

커서를 사용하기 전에 반드시 DECLARE 문으로 선언되어 있는지 확인하세요.

-- 잘못된 예시: 선언 없이 FETCH 시도
BEGIN;
FETCH NEXT FROM my_cursor; -- ERROR: 34000 invalid cursor name
COMMIT;

-- 올바른 예시: 커서 선언 후 사용
BEGIN;

DECLARE my_cursor CURSOR FOR
    SELECT id, name, email
    FROM users
    WHERE active = true
    ORDER BY id;

FETCH NEXT FROM my_cursor;
-- id | name  | email
-- 1  | Alice | alice@example.com

FETCH 5 FROM my_cursor; -- 다음 5행 가져오기

CLOSE my_cursor;
COMMIT;

원인 2 해결: WITH HOLD 커서 사용 (트랜잭션 경계 문제)

트랜잭션 커밋 이후에도 커서를 계속 사용해야 한다면 WITH HOLD 옵션을 사용하세요.

-- 잘못된 예시: 트랜잭션 종료 후 커서 접근
BEGIN;
DECLARE tx_cursor CURSOR FOR
    SELECT * FROM orders WHERE status = 'pending';
COMMIT; -- 커서가 자동으로 닫힘

FETCH NEXT FROM tx_cursor; -- ERROR: 34000 invalid cursor name

-- 올바른 예시: WITH HOLD 옵션으로 트랜잭션 외부에서도 사용 가능
BEGIN;
DECLARE persistent_cursor CURSOR WITH HOLD FOR
    SELECT id, order_date, amount
    FROM orders
    WHERE status = 'pending'
    ORDER BY order_date;
COMMIT; -- 커서가 유지됨

-- 트랜잭션 외부에서도 접근 가능
FETCH ALL FROM persistent_cursor;

CLOSE persistent_cursor; -- 명시적으로 닫아야 함

원인 3 해결: PL/pgSQL에서 커서 재사용 처리

PL/pgSQL 함수나 프로시저에서 커서 재사용 시 상태를 명확히 관리하세요.

-- 올바른 PL/pgSQL 커서 사용 예시
CREATE OR REPLACE FUNCTION process_users_cursor()
RETURNS void
LANGUAGE plpgsql
AS $$
DECLARE
    -- 커서 변수 선언
    user_cursor CURSOR FOR
        SELECT id, name, email
        FROM users
        WHERE active = true;
    
    user_record RECORD;
    processed_count INTEGER := 0;
BEGIN
    -- 커서 열기
    OPEN user_cursor;
    
    LOOP
        -- 한 행씩 가져오기
        FETCH user_cursor INTO user_record;
        
        -- 더 이상 데이터가 없으면 종료
        EXIT WHEN NOT FOUND;
        
        -- 비즈니스 로직 처리
        UPDATE users
        SET last_processed = NOW()
        WHERE id = user_record.id;
        
        processed_count := processed_count + 1;
        
        -- 1000건마다 로그 출력
        IF processed_count % 1000 = 0 THEN
            RAISE NOTICE '처리 완료: % 건', processed_count;
        END IF;
    END LOOP;
    
    -- 커서 닫기 (명시적으로)
    CLOSE user_cursor;
    
    RAISE NOTICE '총 처리 건수: %', processed_count;

EXCEPTION
    WHEN OTHERS THEN
        -- 예외 발생 시에도 커서 닫기 시도
        -- PL/pgSQL에서는 이미 닫힌 커서 CLOSE 시도는 무시됨
        BEGIN
            CLOSE user_cursor;
        EXCEPTION
            WHEN invalid_cursor_name THEN
                NULL; -- 이미 닫힌 경우 무시
        END;
        RAISE;
END;
$$;

-- 함수 실행
SELECT process_users_cursor();

커서 상태 동적 확인

현재 세션에서 열린 커서를 확인하려면 pg_cursors 뷰를 활용하세요.

-- 현재 세션에서 열린 커서 목록 조회
SELECT name,
       statement,
       is_holdable,
       is_binary,
       is_scrollable,
       creation_time
FROM pg_cursors;

-- 특정 커서 이름이 존재하는지 확인
SELECT EXISTS (
    SELECT 1
    FROM pg_cursors
    WHERE name = 'my_cursor'
) AS cursor_exists;

예방 방법

1. 커서 생명주기 명확한 관리 패턴 적용

커서는 반드시 DECLARE → OPEN → FETCH → CLOSE 순서로 관리하며, 예외 상황에서도 안전하게 닫힐 수 있도록 예외 처리 블록을 항상 포함하세요. PL/pgSQL에서는 커서 변수를 함수 상단에 명시적으로 선언하고, 사용 후 반드시 CLOSE를 호출하는 코딩 컨벤션을 팀 내에서 표준화하는 것이 중요합니다. pg_cursors 시스템 뷰를 활용하여 개발 및 테스트 단계에서 커서 누수(cursor leak)를 정기적으로 점검하면 운영 환경의 에러를 사전에 방지할 수 있습니다.

2. WITH HOLD 커서 사용 시 명시적 종료 보장

WITH HOLD 옵션을 사용하는 커서는 트랜잭션 종료 후에도 살아있으므로, 반드시 작업 완료 후 CLOSE 명령을 명시적으로 호출해야 합니다. 장시간 열려 있는 커서는 서버 메모리를 소비하고 잠금(lock)을 유지할 수 있으므로, 애플리케이션 레벨에서 커서 사용 후 항상 정리하는 로직을 포함시키세요. 특히 JDBC나 psycopg2 등 드라이버를 통해 서버 사이드 커서를 사용할 때는 연결 종료(connection close) 시 자동으로 커서가 닫히는지 드라이버 동작 방식을 반드시 확인하세요.


관련 에러

  • 34001 (cursor already exists): 동일한 이름의 커서를 중복으로 선언할 때 발생합니다. DECLARE 전에 pg_cursors 뷰로 커서 존재 여부를 확인하세요.
  • 25P01 (no active SQL transaction): WITH HOLD 없이 트랜잭션 외부에서 커서를 선언하려 할 때 발생합니다.
  • 55000 (object not in prerequisite state): 커서가 올바른 상태가 아닐 때(예: OPEN되지 않은 커서에 FETCH 시도) 발생할 수 있습니다.
  • 42P01 (undefined table): 커서 정의 내의 쿼리에서 참조하는 테이블이 존재하지 않을 때 발생하며, 커서 선언 실패로 이어져 34000 에러의 간접적 원인이 될 수 있습니다.

DBMS 에러 코드 시리즈

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

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

댓글 남기기