2026년 09월 01일 | DBMS Error 가이드
이 글에서 다루는 내용
34000 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.
34000 invalid cursor name 는?
PostgreSQL 에러 코드 34000은 invalid cursor name으로, 데이터베이스 내에서 존재하지 않거나 잘못된 이름의 커서(Cursor)를 참조하려 할 때 발생합니다. 커서는 대용량 결과 집합을 행 단위로 처리하기 위해 사용되는 데이터베이스 객체인데, 선언되지 않은 커서 이름을 FETCH, MOVE, CLOSE 등의 명령어에 사용하거나, 이미 닫힌 커서를 다시 참조하는 경우에 이 에러가 발생합니다. 특히 트랜잭션이 종료되어 커서가 자동으로 소멸된 이후에도 해당 커서를 참조하려는 코드가 남아있을 때 실무에서 자주 목격되는 에러입니다.
주요 발생 원인
1. 선언되지 않은 커서 이름 참조
가장 흔한 원인은 DECLARE 구문으로 커서를 선언하지 않고 곧바로 FETCH나 CLOSE 명령을 실행하는 경우입니다. 오타나 변수 바인딩 실수로 커서 이름이 잘못 지정된 경우에도 동일한 에러가 발생하며, 대소문자 혼용 문제도 종종 원인이 됩니다.
2. 트랜잭션 종료 후 커서 참조
PostgreSQL에서 WITHOUT HOLD 옵션(기본값)으로 선언된 커서는 트랜잭션이 COMMIT 또는 ROLLBACK되면 자동으로 소멸됩니다. 트랜잭션이 이미 종료된 상태에서 해당 커서를 다시 사용하려 하면 34000 에러가 발생하며, 특히 애플리케이션 레이어에서 커넥션 풀링을 사용할 때 이 문제가 자주 발생합니다.
3. PL/pgSQL 또는 애플리케이션 코드에서의 동적 커서 이름 오류
동적으로 커서 이름을 생성하거나, PL/pgSQL 함수 내에서 커서 변수 관리를 잘못하는 경우에도 이 에러가 발생합니다. 특히 루프 내에서 커서를 반복 사용하거나, 서로 다른 세션 간에 커서 이름이 충돌할 때 디버깅이 어려운 형태로 나타납니다.
해결 방법
원인 1 해결: 커서 선언 확인 및 올바른 이름 사용
커서를 사용하기 전에 반드시 DECLARE로 먼저 선언해야 합니다.
-- 잘못된 예: 커서를 선언하지 않고 FETCH 시도
BEGIN;
FETCH ALL FROM my_cursor; -- ERROR: 34000 invalid cursor name
-- 올바른 예: 커서를 먼저 DECLARE한 후 사용
BEGIN;
DECLARE my_cursor CURSOR FOR
SELECT id, name, email FROM users WHERE active = true ORDER BY id;
FETCH 10 FROM my_cursor; -- 10개 행 가져오기
FETCH NEXT FROM my_cursor; -- 다음 행 가져오기
MOVE FORWARD 5 IN my_cursor; -- 5행 앞으로 이동
CLOSE my_cursor;
COMMIT;
커서 이름이 실제로 존재하는지 확인하려면 아래 시스템 뷰를 조회할 수 있습니다.
-- 현재 세션에서 열려 있는 커서 목록 조회
SELECT name, statement, is_holdable, creation_time
FROM pg_cursors;
원인 2 해결: WITH HOLD 옵션으로 트랜잭션 경계를 넘는 커서 사용
트랜잭션이 종료되더라도 커서를 유지하고 싶다면 WITH HOLD 옵션을 사용하세요.
-- WITHOUT HOLD (기본값): 트랜잭션 종료 시 커서 소멸
BEGIN;
DECLARE temp_cursor CURSOR FOR
SELECT * FROM orders WHERE created_at > NOW() - INTERVAL '7 days';
COMMIT; -- 이 시점에서 커서 소멸
-- 이후 FETCH 시도 시 34000 에러 발생
FETCH ALL FROM temp_cursor; -- ERROR!
-- WITH HOLD: 트랜잭션 종료 후에도 커서 유지
BEGIN;
DECLARE holdable_cursor CURSOR WITH HOLD FOR
SELECT order_id, customer_id, total_amount
FROM orders
WHERE status = 'pending'
ORDER BY created_at;
COMMIT; -- 트랜잭션 종료 후에도 커서 유지됨
-- 트랜잭션 외부에서도 사용 가능
FETCH 20 FROM holdable_cursor;
CLOSE holdable_cursor; -- 반드시 명시적으로 닫아야 함
원인 3 해결: PL/pgSQL 함수에서 안전한 커서 사용
PL/pgSQL 내에서 커서를 사용할 때는 커서 변수를 명시적으로 선언하고, 예외 처리를 함께 구현하는 것이 안전합니다.
-- PL/pgSQL에서 안전한 커서 사용 예제
CREATE OR REPLACE FUNCTION process_large_dataset()
RETURNS void AS $$
DECLARE
-- 커서 변수 선언
v_cursor CURSOR FOR
SELECT id, data_column FROM large_table WHERE processed = false;
v_row large_table%ROWTYPE;
v_batch_size INT := 1000;
v_count INT := 0;
BEGIN
OPEN v_cursor;
LOOP
FETCH v_cursor INTO v_row;
EXIT WHEN NOT FOUND; -- 더 이상 데이터가 없으면 루프 종료
-- 각 행에 대한 처리 로직
UPDATE large_table
SET processed = true, updated_at = NOW()
WHERE id = v_row.id;
v_count := v_count + 1;
-- 배치 단위로 커밋 (WITH HOLD 커서 사용 시)
IF v_count % v_batch_size = 0 THEN
RAISE NOTICE '% rows processed', v_count;
END IF;
END LOOP;
CLOSE v_cursor;
RAISE NOTICE 'Total processed: % rows', v_count;
EXCEPTION
WHEN invalid_cursor_name THEN
RAISE WARNING '커서를 찾을 수 없습니다: %', SQLERRM;
-- 필요 시 커서 정리 로직 추가
WHEN OTHERS THEN
-- 커서가 열려있을 경우 안전하게 닫기
IF v_cursor%ISOPEN THEN
CLOSE v_cursor;
END IF;
RAISE;
END;
$$ LANGUAGE plpgsql;
동적 커서 이름을 사용해야 하는 경우에는 아래와 같이 REFCURSOR를 활용하세요.
-- REFCURSOR를 이용한 동적 커서 처리
CREATE OR REPLACE FUNCTION get_data_cursor(p_table_name TEXT)
RETURNS refcursor AS $$
DECLARE
v_cursor REFCURSOR := 'dynamic_cursor_' || p_table_name;
v_query TEXT;
BEGIN
v_query := 'SELECT * FROM ' || quote_ident(p_table_name) || ' LIMIT 1000';
OPEN v_cursor FOR EXECUTE v_query;
RETURN v_cursor;
END;
$$ LANGUAGE plpgsql;
-- 함수 호출 및 커서 사용
BEGIN;
SELECT get_data_cursor('users'); -- 커서 이름 반환: dynamic_cursor_users
FETCH ALL FROM dynamic_cursor_users;
CLOSE dynamic_cursor_users;
COMMIT;
예방 방법
1. 커서 사용 전 존재 여부 확인 및 명시적 관리 패턴 적용
pg_cursors 시스템 뷰를 활용해 커서 상태를 주기적으로 점검하고, 커서의 생명주기(OPEN → FETCH → CLOSE)를 코드 구조상 명확하게 분리하여 관리하세요. PL/pgSQL에서는 EXCEPTION 블록에서 invalid_cursor_name(SQLSTATE 34000) 핸들러를 반드시 구현하고, 함수 종료 전에 열린 커서가 있으면 반드시 닫도록 코딩 컨벤션을 정립하세요.
-- 커서 상태 모니터링 쿼리
SELECT
name AS cursor_name,
statement,
is_holdable,
is_binary,
is_scrollable,
creation_time
FROM pg_cursors
ORDER BY creation_time DESC;
2. 커넥션 풀 환경에서의 커서 관리 전략 수립
PgBouncer나 애플리케이션 레벨 커넥션 풀을 사용하는 환경에서는 WITH HOLD 커서 사용 시 반드시 명시적인 CLOSE 구문을 실행하여 커서 누수를 방지하세요. 커넥션이 풀로 반환되기 전에 모든 커서가 닫혔는지 확인하는 로직을 애플리케이션 계층에서 강제화하고, 가능하면 커서 사용을 트랜잭션 단위로 완결시키는 구조로 설계하세요.
관련 에러
24000(invalid_transaction_state): 부적절한 트랜잭션 상태에서 커서를 사용하려 할 때 발생하며, 34000과 함께 나타나는 경우가 많습니다.42P01(undefined_table): 커서 선언 시 참조하는 테이블이 존재하지 않을 때 발생하며, 커서 생성 자체가 실패합니다.55000(object_not_in_prerequisite_state): 커서가 올바른 상태가 아닐 때 발생하며, 이미 닫힌 커서에 FETCH를 시도하는 경우와 유사한 맥락입니다.25P02(in_failed_sql_transaction): 트랜잭션 오류 이후 같은 트랜잭션 내에서 커서 작업을 계속 시도할 때 연쇄적으로 발생할 수 있습니다.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.