2026년 06월 28일 | DBMS Error 가이드
이 글에서 다루는 내용
ORA-01023 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.
ORA-01023 cursor context not found (Invalid cursor number) 는?
ORA-01023 에러는 Oracle 데이터베이스에서 유효하지 않거나 이미 닫힌 커서(Cursor)를 참조하려 할 때 발생하는 오류입니다. 커서는 SQL 쿼리 결과를 처리하기 위한 메모리 상의 작업 영역인데, 해당 커서가 존재하지 않거나 이미 닫혀 있는 상태에서 페치(FETCH) 또는 기타 커서 관련 작업을 시도할 때 이 에러가 발생합니다. 주로 PL/SQL 블록, Pro*C, OCI(Oracle Call Interface), JDBC 등 다양한 프로그래밍 환경에서 커서를 부적절하게 관리할 때 나타납니다.
주요 발생 원인
1. 이미 닫힌 커서에 접근 (CLOSE 후 FETCH 시도)
가장 흔한 원인으로, 커서를 CLOSE한 이후에 다시 FETCH하거나 %ROWCOUNT, %FOUND 등의 커서 속성을 참조할 때 발생합니다. PL/SQL 코드에서 루프 종료 후 커서를 닫고, 이후 로직에서 실수로 다시 해당 커서를 참조하는 경우가 대표적입니다. 코드가 복잡해질수록 커서의 생명 주기를 추적하기 어려워지므로 특히 주의해야 합니다.
2. 동적 SQL에서의 커서 번호 오류 (DBMS_SQL 패키지 오용)
DBMS_SQL 패키지를 사용하는 동적 SQL 환경에서 OPEN_CURSOR로 발급받은 커서 번호를 잘못 관리할 때 발생합니다. 예를 들어, 커서를 CLOSE_CURSOR로 닫은 후 동일한 커서 번호 변수를 재사용하거나, 커서 번호를 초기화(NULL 처리)하지 않고 다시 EXECUTE나 FETCH_ROWS를 호출하는 경우입니다. 특히 예외 처리 블록에서 커서를 강제로 닫은 뒤 이후 로직에서 동일 변수를 참조할 때 자주 나타납니다.
3. 멀티 세션 또는 커넥션 풀 환경에서의 커서 공유 문제
커넥션 풀(Connection Pool) 환경에서 특정 세션에 귀속된 커서를 다른 세션이나 스레드에서 참조하려 할 때 발생할 수 있습니다. 커서는 세션 레벨의 자원이기 때문에 세션이 종료되거나 연결이 재사용될 경우 이전 커서 컨텍스트는 무효화됩니다. WAS(Web Application Server)의 커넥션 풀 설정이 부적절하거나 커서를 전역 변수로 선언해 세션 간 공유를 시도하는 잘못된 설계가 원인인 경우가 많습니다.
해결 방법
원인 1 해결: 커서 상태 확인 후 접근
커서를 닫기 전에 커서가 열려 있는지 확인하는 습관을 들이고, %ISOPEN 속성을 적극 활용하세요.
DECLARE
CURSOR emp_cur IS
SELECT employee_id, first_name, salary
FROM employees
WHERE department_id = 10;
v_emp employees%ROWTYPE;
BEGIN
OPEN emp_cur;
LOOP
FETCH emp_cur INTO v_emp.employee_id, v_emp.first_name, v_emp.salary;
EXIT WHEN emp_cur%NOTFOUND;
DBMS_OUTPUT.PUT_LINE('ID: ' || v_emp.employee_id ||
', Name: ' || v_emp.first_name ||
', Salary: ' || v_emp.salary);
END LOOP;
-- %ISOPEN으로 커서 상태 확인 후 안전하게 닫기
IF emp_cur%ISOPEN THEN
CLOSE emp_cur;
END IF;
-- 커서 닫힌 후 접근 시도하지 않음 (ORA-01023 방지)
-- 아래와 같은 코드는 절대 사용하지 말 것:
-- FETCH emp_cur INTO v_emp; -- 에러 발생!
EXCEPTION
WHEN OTHERS THEN
-- 예외 발생 시에도 커서 정리
IF emp_cur%ISOPEN THEN
CLOSE emp_cur;
END IF;
RAISE;
END;
/
원인 2 해결: DBMS_SQL 패키지 커서 번호 안전하게 관리
DBMS_SQL을 사용할 때는 커서 번호 변수를 NULL로 초기화하고, 예외 처리 블록에서 반드시 커서를 닫도록 구성하세요.
DECLARE
v_cursor_id INTEGER;
v_sql VARCHAR2(500);
v_result INTEGER;
v_emp_id NUMBER;
v_emp_name VARCHAR2(100);
BEGIN
-- 커서 번호 초기화
v_cursor_id := NULL;
v_sql := 'SELECT employee_id, first_name FROM employees WHERE rownum <= 5';
-- 커서 열기
v_cursor_id := DBMS_SQL.OPEN_CURSOR;
-- SQL 파싱
DBMS_SQL.PARSE(v_cursor_id, v_sql, DBMS_SQL.NATIVE);
-- 컬럼 정의
DBMS_SQL.DEFINE_COLUMN(v_cursor_id, 1, v_emp_id);
DBMS_SQL.DEFINE_COLUMN(v_cursor_id, 2, v_emp_name, 100);
-- 실행
v_result := DBMS_SQL.EXECUTE(v_cursor_id);
-- 페치 루프
LOOP
EXIT WHEN DBMS_SQL.FETCH_ROWS(v_cursor_id) = 0;
DBMS_SQL.COLUMN_VALUE(v_cursor_id, 1, v_emp_id);
DBMS_SQL.COLUMN_VALUE(v_cursor_id, 2, v_emp_name);
DBMS_OUTPUT.PUT_LINE(v_emp_id || ' - ' || v_emp_name);
END LOOP;
-- 사용 후 반드시 닫기
IF DBMS_SQL.IS_OPEN(v_cursor_id) THEN
DBMS_SQL.CLOSE_CURSOR(v_cursor_id);
END IF;
EXCEPTION
WHEN OTHERS THEN
-- 예외 시에도 커서가 열려있으면 닫기 (ORA-01023 예방)
IF v_cursor_id IS NOT NULL AND DBMS_SQL.IS_OPEN(v_cursor_id) THEN
DBMS_SQL.CLOSE_CURSOR(v_cursor_id);
END IF;
DBMS_OUTPUT.PUT_LINE('에러 발생: ' || SQLERRM);
RAISE;
END;
/
원인 3 해결: 커넥션 풀 환경에서 커서 범위 제한
커서를 전역 변수나 패키지 변수로 선언하지 말고, 항상 로컬 스코프 내에서 선언하고 사용 후 즉시 닫는 구조를 유지하세요.
-- 잘못된 패턴 (패키지 레벨 커서 - 세션 간 공유 시 ORA-01023 위험)
/*
CREATE OR REPLACE PACKAGE bad_pkg AS
CURSOR g_emp_cur IS SELECT * FROM employees; -- 위험!
END bad_pkg;
*/
-- 올바른 패턴: 프로시저 내부에서 로컬 커서 사용
CREATE OR REPLACE PROCEDURE get_employees_safe(
p_dept_id IN NUMBER
) AS
-- 로컬 커서로 선언 (프로시저 스코프)
CURSOR l_emp_cur IS
SELECT employee_id, first_name, salary
FROM employees
WHERE department_id = p_dept_id;
v_emp_id employees.employee_id%TYPE;
v_emp_name employees.first_name%TYPE;
v_salary employees.salary%TYPE;
BEGIN
OPEN l_emp_cur;
LOOP
FETCH l_emp_cur INTO v_emp_id, v_emp_name, v_salary;
EXIT WHEN l_emp_cur%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(
'EMP: ' || v_emp_id ||
' | ' || v_emp_name ||
' | Salary: ' || v_salary
);
END LOOP;
CLOSE l_emp_cur;
EXCEPTION
WHEN OTHERS THEN
IF l_emp_cur%ISOPEN THEN
CLOSE l_emp_cur;
END IF;
RAISE;
END get_employees_safe;
/
-- 실행 테스트
BEGIN
get_employees_safe(10);
END;
/
예방 방법
1. 커서 생명 주기 관리 원칙 수립 (OPEN-FETCH-CLOSE 패턴 준수)
모든 PL/SQL 코드에서 커서는 반드시 OPEN → FETCH → CLOSE 순서를 지키고, 예외 처리 블록(EXCEPTION 절)에서도 %ISOPEN 또는 DBMS_SQL.IS_OPEN 체크 후 커서를 닫는 코드를 의무적으로 작성하는 팀 코딩 컨벤션을 수립하세요. 코드 리뷰 시 커서 닫힘 여부를 체크리스트 항목으로 포함시키면 운영 환경에서의 실수를 크게 줄일 수 있습니다.
2. FOR 루프 커서 활용으로 자동 관리
명시적 커서를 직접 관리하는 대신, 가능한 경우 FOR ... IN cursor 구문을 사용하면 Oracle이 자동으로 커서의 OPEN, FETCH, CLOSE를 처리해주므로 ORA-01023과 같은 커서 관련 에러를 원천적으로 방지할 수 있습니다.
-- FOR 루프 사용 시 커서 자동 관리 (권장 방식)
BEGIN
FOR emp_rec IN (
SELECT employee_id, first_name, salary
FROM employees
WHERE department_id = 20
) LOOP
DBMS_OUTPUT.PUT_LINE(
emp_rec.employee_id || ' | ' ||
emp_rec.first_name || ' | ' ||
emp_rec.salary
);
END LOOP;
-- 커서 자동으로 닫힘 - ORA-01023 발생 없음
END;
/
관련 에러
- ORA-01001: invalid cursor — 커서가 유효하지 않을 때 발생하며, ORA-01023과 유사하지만 커서 번호 자체가 잘못된 경우에 해당합니다.
- ORA-01002: fetch out of sequence — 커서에서 순서에 맞지 않는 FETCH 작업을 시도할 때 발생하며, SELECT FOR UPDATE 커서에서 커밋 후 FETCH 시 자주 나타납니다.
- ORA-06511: PL/SQL: cursor already open — 이미 열린 커서를 다시 OPEN하려 할 때 발생하는 에러로, 커서 생명주기 관리 실수에서 비롯됩니다.
- ORA-01000: maximum open cursors exceeded — 세션에서 허용된 최대 커서 수를 초과할 때 발생하며, 커서를 닫지 않고 계속 열 경우 발생합니다.
OPEN_CURSORS파라미터 조정이 필요합니다.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.