Oracle ORA-01002 오류 원인과 해결 방법 완벽 가이드

ORA-01002
2026년 06월 26일 | DBMS Error 가이드

이 글에서 다루는 내용

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

ORA-01002 fetch out of sequence 는?

ORA-01002 에러는 Oracle 커서(Cursor)를 사용하는 과정에서 커서의 상태와 맞지 않는 순서로 FETCH 작업을 시도할 때 발생하는 에러입니다. 쉽게 말해, 이미 닫혔거나 무효화된 커서에서 데이터를 가져오려 하거나, 트랜잭션의 변경으로 인해 커서가 무효화된 상태에서 FETCH를 계속 시도할 때 Oracle이 이 에러를 발생시킵니다. 주로 PL/SQL 코드, Pro*C, JDBC, OCI 등 커서를 직접 제어하는 환경에서 자주 목격되며, 특히 FOR UPDATE 커서와 COMMIT/ROLLBACK을 잘못 조합했을 때 빈번하게 나타납니다.


주요 발생 원인

1. FOR UPDATE 커서 사용 중 COMMIT 또는 ROLLBACK 실행

이것이 가장 흔하고 실무에서 가장 많이 마주치는 원인입니다. SELECT ... FOR UPDATE 구문으로 열린 커서는 행 잠금(Row Lock)을 유지하는 커서인데, FETCH 루프 중간에 COMMIT 혹은 ROLLBACK을 실행하면 Oracle은 해당 커서를 자동으로 무효화(invalidate)시킵니다. 이 상태에서 다음 FETCH를 시도하면 ORA-01002가 발생합니다.

-- 잘못된 예시: FOR UPDATE 커서 루프 중 COMMIT
DECLARE
    CURSOR c_emp IS
        SELECT employee_id, salary
        FROM employees
        FOR UPDATE OF salary;  -- 행 잠금 커서
    v_emp c_emp%ROWTYPE;
BEGIN
    OPEN c_emp;
    LOOP
        FETCH c_emp INTO v_emp;
        EXIT WHEN c_emp%NOTFOUND;

        UPDATE employees
        SET salary = v_emp.salary * 1.1
        WHERE CURRENT OF c_emp;

        COMMIT;  -- ★ 이 시점에 커서가 무효화됨 → 다음 FETCH에서 ORA-01002 발생
    END LOOP;
    CLOSE c_emp;
END;
/

2. 이미 닫힌(CLOSE) 커서에 FETCH 시도

커서를 명시적으로 CLOSE한 이후에 다시 FETCH를 시도하거나, 예외(Exception) 처리 과정에서 커서가 닫힌 후 루프가 계속 실행되는 경우에 발생합니다. 특히 예외 핸들러 없이 작성된 복잡한 PL/SQL 블록에서 커서의 생명 주기를 추적하지 못할 때 이 문제가 생깁니다. 커서 상태를 %ISOPEN 속성으로 체크하지 않으면 디버깅이 매우 어려워집니다.

-- 잘못된 예시: 이미 닫힌 커서에 FETCH
DECLARE
    CURSOR c_dept IS
        SELECT department_id, department_name
        FROM departments;
    v_dept c_dept%ROWTYPE;
BEGIN
    OPEN c_dept;
    FETCH c_dept INTO v_dept;  -- 첫 번째 FETCH
    CLOSE c_dept;              -- 커서 닫힘

    FETCH c_dept INTO v_dept;  -- ★ ORA-01002 발생: 이미 닫힌 커서에 FETCH
END;
/

3. REF CURSOR 또는 동적 쿼리에서의 잘못된 커서 재사용

REF CURSOR나 DBMS_SQL을 이용한 동적 쿼리 환경에서, 커서를 열기 전에 FETCH를 호출하거나 커서를 재오픈(Re-OPEN)하지 않고 이전 결과를 다시 읽으려 할 때 발생합니다. 동적 SQL 환경에서는 커서 상태 관리가 정적 커서보다 복잡하기 때문에, 특히 프로시저 간 커서를 파라미터로 전달하는 경우 이 에러가 빈번히 발생합니다.

-- 잘못된 예시: REF CURSOR를 OPEN 없이 FETCH 시도
DECLARE
    TYPE ref_cur_type IS REF CURSOR;
    v_cur ref_cur_type;
    v_name VARCHAR2(100);
BEGIN
    -- OPEN 없이 FETCH 시도
    FETCH v_cur INTO v_name;  -- ★ ORA-01002 발생
END;
/

해결 방법

원인 1 해결: FOR UPDATE 커서에서 COMMIT 분리

FOR UPDATE 커서를 사용할 때는 루프 내에서 COMMIT을 실행하지 않는 것이 원칙입니다. 만약 중간 커밋이 반드시 필요한 경우, BULK COLLECT와 FORALL을 활용하거나, 배치 단위로 데이터를 끊어서 처리하는 방식으로 전환해야 합니다.

-- 올바른 예시 1: 루프 외부에서 단일 COMMIT
DECLARE
    CURSOR c_emp IS
        SELECT employee_id, salary
        FROM employees
        FOR UPDATE OF salary;
    v_emp c_emp%ROWTYPE;
BEGIN
    OPEN c_emp;
    LOOP
        FETCH c_emp INTO v_emp;
        EXIT WHEN c_emp%NOTFOUND;

        UPDATE employees
        SET salary = v_emp.salary * 1.1
        WHERE CURRENT OF c_emp;
        -- COMMIT 제거, 루프 종료 후 한 번만 실행
    END LOOP;
    CLOSE c_emp;
    COMMIT;  -- ★ 루프 바깥에서 한 번만 COMMIT
END;
/

-- 올바른 예시 2: BULK COLLECT + FORALL로 대량 처리 (FOR UPDATE 불필요)
DECLARE
    TYPE emp_id_tbl IS TABLE OF employees.employee_id%TYPE;
    TYPE salary_tbl IS TABLE OF employees.salary%TYPE;
    v_emp_ids  emp_id_tbl;
    v_salaries salary_tbl;
BEGIN
    SELECT employee_id, salary
    BULK COLLECT INTO v_emp_ids, v_salaries
    FROM employees
    WHERE department_id = 10;

    FORALL i IN 1..v_emp_ids.COUNT
        UPDATE employees
        SET salary = v_salaries(i) * 1.1
        WHERE employee_id = v_emp_ids(i);

    COMMIT;
END;
/

원인 2 해결: 커서 상태 체크 및 예외 처리 강화

커서를 FETCH하기 전에 반드시 %ISOPEN 속성으로 커서가 열려 있는지 확인하고, 예외 발생 시 EXCEPTION 블록에서 커서를 안전하게 닫는 로직을 추가해야 합니다.

-- 올바른 예시: %ISOPEN 체크 및 EXCEPTION 처리
DECLARE
    CURSOR c_dept IS
        SELECT department_id, department_name
        FROM departments;
    v_dept c_dept%ROWTYPE;
BEGIN
    IF NOT c_dept%ISOPEN THEN
        OPEN c_dept;
    END IF;

    LOOP
        FETCH c_dept INTO v_dept;
        EXIT WHEN c_dept%NOTFOUND;
        DBMS_OUTPUT.PUT_LINE('부서: ' || v_dept.department_name);
    END LOOP;

    IF c_dept%ISOPEN THEN
        CLOSE c_dept;
    END IF;

EXCEPTION
    WHEN OTHERS THEN
        IF c_dept%ISOPEN THEN
            CLOSE c_dept;  -- ★ 예외 발생 시에도 반드시 커서 닫기
        END IF;
        RAISE;
END;
/

원인 3 해결: REF CURSOR 올바른 사용

REF CURSOR는 반드시 OPEN 후 FETCH해야 하며, 재사용이 필요한 경우 명시적으로 CLOSE 후 다시 OPEN해야 합니다.

-- 올바른 예시: REF CURSOR 정상 사용
DECLARE
    TYPE ref_cur_type IS REF CURSOR;
    v_cur  ref_cur_type;
    v_name employees.first_name%TYPE;
BEGIN
    OPEN v_cur FOR
        SELECT first_name
        FROM employees
        WHERE department_id = 20;

    LOOP
        FETCH v_cur INTO v_name;
        EXIT WHEN v_cur%NOTFOUND;
        DBMS_OUTPUT.PUT_LINE('직원: ' || v_name);
    END LOOP;

    CLOSE v_cur;  -- ★ 사용 후 반드시 닫기
END;
/

예방 방법

1. FOR UPDATE 커서와 DML 트랜잭션 설계 원칙 준수

SELECT ... FOR UPDATE 커서를 사용하는 PL/SQL 코드를 작성할 때는 루프 내부에서 절대 COMMIT 또는 ROLLBACK을 수행하지 않는다는 원칙을 팀 코딩 규약으로 명문화하세요. 대량 데이터 처리가 필요한 경우에는 처음부터 BULK COLLECT / FORALL 패턴을 사용하여 커서 루프 자체를 최소화하는 방향으로 설계하면, ORA-01002뿐 아니라 성능 문제도 동시에 예방할 수 있습니다.

2. 커서 생명 주기 관리 및 코드 리뷰 체계 구축

모든 명시적 커서는 OPEN → FETCH → CLOSE의 생명 주기를 반드시 준수하고, EXCEPTION 블록에서도 %ISOPEN을 이용해 안전하게 닫히도록 작성해야 합니다. 코드 리뷰 체크리스트에 “커서를 사용하는 루프 내 COMMIT/ROLLBACK 존재 여부”와 “EXCEPTION 블록의 커서 CLOSE 여부”를 항목으로 추가하여 배포 전 반드시 검토하는 프로세스를 정착시키세요.


관련 에러

  • ORA-01001 (invalid cursor): 유효하지 않은 커서 번호를 참조할 때 발생하며, ORA-01002와 함께 커서 관련 에러로 자주 언급됩니다.
  • ORA-06511 (PL/SQL: cursor already open): 이미 열려 있는 커서를 다시 OPEN하려 할 때 발생하는 에러로, 커서 생명 주기 관리 실수에서 함께 나타나는 경우가 많습니다.
  • ORA-01003 (no statement parsed): DBMS_SQL 환경에서 커서에 SQL이 파싱되지 않은 상태에서 실행을 시도할 때 발생하며, 동적 커서 관련 에러 그룹에 속합니다.

DBMS 에러 코드 시리즈

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

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

댓글 남기기