2026년 08월 26일 | DBMS Error 가이드
이 글에서 다루는 내용
24000 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.
24000 invalid cursor state 는?
PostgreSQL 에러 코드 24000, invalid cursor state는 커서(Cursor)가 올바르지 않은 상태에서 커서 관련 작업을 시도할 때 발생하는 에러입니다. 예를 들어, 아직 열리지 않은 커서를 FETCH하거나, 이미 닫힌 커서를 다시 사용하거나, 커서가 결과 집합의 유효하지 않은 위치를 가리킬 때 이 에러가 발생합니다. 주로 PL/pgSQL 프로시저, 함수, 또는 명시적 커서를 사용하는 복잡한 트랜잭션 처리 코드에서 자주 나타나며, 커서의 생명주기 관리가 부적절할 때 발생합니다.
주요 발생 원인
- 커서가 열리지 않은 상태에서 FETCH 또는 CLOSE 시도
커서를 OPEN 하지 않은 상태에서 FETCH 또는 CLOSE 명령을 실행하면 이 에러가 발생합니다. PL/pgSQL에서 커서 변수를 선언만 하고 OPEN을 생략하거나, 조건 분기 로직에 의해 OPEN이 실행되지 않은 채로 FETCH를 호출하는 경우가 대표적입니다. 특히 복잡한 비즈니스 로직에서 OPEN 구문이 조건문 안에 있을 때 실수로 이 상황이 발생합니다.
- 트랜잭션 종료 또는 롤백 후 커서 재사용 시도
PostgreSQL에서 WITHOUT HOLD 옵션으로 선언된 커서(기본값)는 트랜잭션이 종료되거나 롤백되면 자동으로 닫힙니다. 트랜잭션이 COMMIT 또는 ROLLBACK 된 이후에 해당 커서를 계속 사용하려 하면 invalid cursor state 에러가 발생합니다. 애플리케이션 레이어에서 커넥션 풀링이나 재시도 로직이 잘못 구현된 경우 특히 이 문제가 빈번하게 나타납니다.
- 잘못된 커서 스크롤 방향 또는 위치 참조
NO SCROLL 커서에서 FETCH PRIOR, FETCH ABSOLUTE, FETCH RELATIVE 등 역방향 또는 절대 위치 이동 명령을 사용하는 경우에도 이 에러가 발생할 수 있습니다. 커서를 선언할 때 스크롤 가능 여부(SCROLL / NO SCROLL)를 명확히 지정하지 않으면, 기본 동작이 예상과 다를 수 있습니다. 커서가 이미 결과 집합의 끝(EOF)을 넘어선 상태에서 추가 FETCH를 시도하는 경우에도 유사한 문제가 발생합니다.
해결 방법
원인 1 해결: 커서 OPEN 여부 확인 후 FETCH 실행
PL/pgSQL에서 커서를 사용하기 전에 반드시 OPEN 상태인지 확인하거나, 구조적으로 OPEN → FETCH → CLOSE 순서를 보장하는 코드를 작성합니다.
-- 잘못된 예시: OPEN 없이 FETCH 시도
DO $$
DECLARE
cur CURSOR FOR SELECT id, name FROM employees;
rec RECORD;
BEGIN
-- OPEN 없이 FETCH 시도 → 24000 에러 발생
FETCH cur INTO rec;
RAISE NOTICE 'Name: %', rec.name;
END;
$$;
-- 올바른 예시: OPEN → FETCH → CLOSE 순서 준수
DO $$
DECLARE
cur CURSOR FOR SELECT id, name FROM employees;
rec RECORD;
BEGIN
OPEN cur; -- 반드시 OPEN 먼저 실행
FETCH cur INTO rec;
IF FOUND THEN
RAISE NOTICE 'Name: %', rec.name;
END IF;
CLOSE cur; -- 사용 후 반드시 CLOSE
END;
$$;
원인 2 해결: WITH HOLD 커서 사용 또는 커서 생명주기 관리
트랜잭션 경계를 넘어 커서를 사용해야 하는 경우, WITH HOLD 옵션을 사용하여 커서를 선언합니다. 단, WITH HOLD 커서는 결과 집합을 임시 저장하므로 대용량 데이터에서는 메모리 및 디스크 사용량에 주의해야 합니다.
-- WITH HOLD 커서: 트랜잭션 종료 후에도 커서 유지
BEGIN;
DECLARE my_cursor CURSOR WITH HOLD FOR
SELECT id, name, salary FROM employees ORDER BY id;
COMMIT; -- 트랜잭션 커밋 후에도 커서 사용 가능
FETCH 5 FROM my_cursor; -- 커밋 이후에도 정상 동작
FETCH NEXT FROM my_cursor;
CLOSE my_cursor; -- 명시적으로 닫아야 함
-- WITHOUT HOLD(기본값) 커서의 올바른 사용: 트랜잭션 내에서만 사용
BEGIN;
DECLARE temp_cursor CURSOR FOR
SELECT id, name FROM employees WHERE department_id = 10;
FETCH ALL FROM temp_cursor;
CLOSE temp_cursor;
COMMIT; -- 트랜잭션 내에서 모든 커서 작업 완료 후 커밋
원인 3 해결: SCROLL 커서 명시 선언 및 FETCH 방향 제어
역방향 이동이 필요한 경우 반드시 SCROLL 키워드를 명시하고, FETCH 방향과 커서 위치를 명확히 관리합니다.
-- NO SCROLL 커서에서 역방향 이동 시도 (에러 발생)
BEGIN;
DECLARE no_scroll_cur NO SCROLL CURSOR FOR
SELECT id, name FROM employees ORDER BY id;
FETCH NEXT FROM no_scroll_cur; -- 정상
FETCH PRIOR FROM no_scroll_cur; -- 24000 에러! NO SCROLL에서 역방향 불가
CLOSE no_scroll_cur;
COMMIT;
-- SCROLL 커서로 올바르게 선언하여 역방향 이동 지원
BEGIN;
DECLARE scroll_cur SCROLL CURSOR FOR
SELECT id, name FROM employees ORDER BY id;
FETCH NEXT FROM scroll_cur; -- 순방향
FETCH NEXT FROM scroll_cur; -- 순방향
FETCH PRIOR FROM scroll_cur; -- 역방향 (SCROLL이므로 가능)
FETCH ABSOLUTE 1 FROM scroll_cur; -- 절대 위치 이동
FETCH RELATIVE 2 FROM scroll_cur; -- 상대 위치 이동
CLOSE scroll_cur;
COMMIT;
-- PL/pgSQL에서 안전한 커서 루프 처리
DO $$
DECLARE
cur SCROLL CURSOR FOR SELECT id, name FROM employees ORDER BY id;
rec RECORD;
BEGIN
OPEN cur;
LOOP
FETCH NEXT FROM cur INTO rec;
EXIT WHEN NOT FOUND; -- 데이터 없으면 루프 종료 (EOF 초과 방지)
RAISE NOTICE 'Processing: % - %', rec.id, rec.name;
END LOOP;
CLOSE cur;
EXCEPTION
WHEN invalid_cursor_state THEN
-- 24000 에러 발생 시 안전하게 처리
RAISE WARNING '커서 상태 오류가 발생했습니다. 커서를 닫습니다.';
IF cur%ISOPEN THEN
CLOSE cur;
END IF;
END;
$$;
예방 방법
- 커서 생명주기를 명확히 관리하는 코딩 패턴 준수
PL/pgSQL 함수나 프로시저에서 커서를 사용할 때는 반드시 EXCEPTION 블록을 함께 작성하여 예외 발생 시에도 커서가 정상적으로 닫히도록 보장합니다. 또한 커서 변수의 %ISOPEN 속성을 활용하여 FETCH 전에 커서 상태를 확인하는 방어적 코딩 습관을 들이세요.
“`sql
— 방어적 커서 관리 패턴
DO $$
DECLARE
cur CURSOR FOR SELECT id FROM employees;
rec RECORD;
BEGIN
OPEN cur;
LOOP
FETCH cur INTO rec;
EXIT WHEN NOT FOUND;
— 비즈니스 로직 처리
PERFORM process_employee(rec.id);
END LOOP;
CLOSE cur;
EXCEPTION
WHEN OTHERS THEN
— 예외 발생 시에도 커서 리소스 해제 보장
BEGIN
CLOSE cur;
EXCEPTION
WHEN invalid_cursor_state THEN NULL; — 이미 닫힌 경우 무시
END;
RAISE; — 원래 예외 재발생
END;
$$;
“`
- 트랜잭션 범위와 커서 범위를 일치시키는 설계 원칙 적용
가능하면 커서의 사용 범위를 하나의 트랜잭션 내로 제한하고, WITH HOLD 커서는 꼭 필요한 경우에만 사용합니다. 또한 커서를 사용하는 함수나 프로시저에서는 트랜잭션 상태를 명시적으로 관리하고, 커넥션 풀에서 커넥션을 반환하기 전에 모든 열린 커서를 닫는 정책을 애플리케이션 레벨에서 강제화하세요.
관련 에러
- 34000 (invalid cursor name): 존재하지 않는 이름의 커서를 참조할 때 발생합니다.
24000과 혼동하기 쉬우나,34000은 커서 이름 자체가 잘못된 경우입니다. - 25001 (active SQL transaction): 트랜잭션 제어와 관련된 에러로, 커서와 트랜잭션의 잘못된 조합 사용 시 함께 나타날 수 있습니다.
- 55000 (object not in prerequisite state): 객체가 올바른 상태가 아닐 때 발생하는 에러로, 커서 상태 문제와 유사한 맥락에서 발생할 수 있습니다.
- 42P01 (undefined table): 커서를 정의하는 쿼리에서 존재하지 않는 테이블을 참조할 때 발생하며, 커서 OPEN 시점에 에러가 발생합니다.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.