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

ORA-06511
2026년 08월 29일 | DBMS Error 가이드

이 글에서 다루는 내용

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

ORA-06511 PL/SQL: cursor already open 는?

ORA-06511 에러는 PL/SQL 코드에서 이미 열려 있는(OPEN 상태인) 커서를 다시 OPEN하려고 할 때 발생하는 오류입니다. Oracle 데이터베이스는 하나의 커서가 동시에 두 번 열리는 것을 허용하지 않으며, 이미 열린 커서를 닫지 않고 재사용하려 할 경우 이 에러가 트리거됩니다. 주로 루프 내에서 커서를 반복적으로 열거나, 예외 처리 블록에서 커서의 상태를 제대로 관리하지 않을 때 실무에서 빈번하게 나타납니다.


주요 발생 원인

  • OPEN 전에 커서 상태 확인 없이 중복 OPEN 시도

가장 흔한 원인으로, 커서를 OPEN하기 전에 해당 커서가 이미 열려 있는지 %ISOPEN 속성으로 확인하지 않고 무조건 OPEN을 호출하는 경우입니다. 예를 들어 프로시저가 반복 호출되거나, 하나의 PL/SQL 블록 안에서 같은 커서를 두 번 OPEN하는 로직이 섞여 있을 때 발생합니다. 특히 대규모 패키지 내에서 여러 프로시저가 동일한 패키지 수준 커서를 공유할 때 놓치기 쉬운 실수입니다.

  • 예외(Exception) 처리 블록에서 커서가 닫히지 않은 채 재실행

PL/SQL 블록 실행 중 예외가 발생했을 때, EXCEPTION 블록에서 커서를 CLOSE하지 않고 그대로 종료되거나 재시도 로직이 실행될 경우 커서는 열린 상태로 남아 있게 됩니다. 이후 동일한 블록 또는 프로시저가 다시 호출되면 커서가 이미 열린 상태이므로 ORA-06511이 발생합니다. 이는 트랜잭션 재시도 패턴을 구현할 때 특히 위험한 패턴입니다.

  • FOR LOOP 커서와 명시적 커서를 혼용하는 경우

커서 FOR LOOP는 Oracle이 내부적으로 커서의 OPEN, FETCH, CLOSE를 자동으로 처리해 줍니다. 그런데 개발자가 FOR LOOP 내에서 같은 커서를 명시적으로 다시 OPEN하거나, FOR LOOP 전에 이미 수동으로 커서를 OPEN해 둔 상태에서 FOR LOOP를 실행하면 충돌이 발생합니다. 커서 관리 방식을 혼용하는 것은 유지보수성도 낮추고 예기치 않은 에러의 원인이 됩니다.


해결 방법

원인 1 해결: %ISOPEN 속성으로 커서 상태 확인 후 OPEN

커서를 열기 전에 반드시 %ISOPEN 속성을 사용하여 커서가 이미 열려 있는지 확인하고, 열려 있다면 먼저 닫은 후 다시 여는 방식으로 처리합니다.

DECLARE
    CURSOR emp_cur IS
        SELECT employee_id, first_name, salary
        FROM employees
        WHERE department_id = 10;
    v_emp_id   employees.employee_id%TYPE;
    v_name     employees.first_name%TYPE;
    v_salary   employees.salary%TYPE;
BEGIN
    -- 커서가 이미 열려 있는지 확인
    IF emp_cur%ISOPEN THEN
        CLOSE emp_cur;  -- 이미 열려 있으면 먼저 닫기
    END IF;

    OPEN emp_cur;  -- 안전하게 커서 열기

    LOOP
        FETCH emp_cur INTO v_emp_id, v_name, v_salary;
        EXIT WHEN emp_cur%NOTFOUND;
        DBMS_OUTPUT.PUT_LINE('ID: ' || v_emp_id || ', Name: ' || v_name || ', Salary: ' || v_salary);
    END LOOP;

    CLOSE emp_cur;  -- 작업 후 반드시 닫기
EXCEPTION
    WHEN OTHERS THEN
        IF emp_cur%ISOPEN THEN
            CLOSE emp_cur;  -- 예외 발생 시에도 반드시 닫기
        END IF;
        RAISE;
END;
/

원인 2 해결: EXCEPTION 블록에서 커서 안전하게 닫기

예외 처리 블록에서 커서를 명시적으로 닫아 다음 실행 시 커서가 열린 상태로 남지 않도록 처리합니다.

DECLARE
    CURSOR dept_cur IS
        SELECT department_id, department_name
        FROM departments;
    v_dept_id   departments.department_id%TYPE;
    v_dept_name departments.department_name%TYPE;
BEGIN
    OPEN dept_cur;

    LOOP
        FETCH dept_cur INTO v_dept_id, v_dept_name;
        EXIT WHEN dept_cur%NOTFOUND;

        -- 의도적으로 예외 유발 가능한 작업
        IF v_dept_id = 0 THEN
            RAISE_APPLICATION_ERROR(-20001, '잘못된 부서 ID입니다.');
        END IF;

        DBMS_OUTPUT.PUT_LINE('Dept: ' || v_dept_name);
    END LOOP;

    CLOSE dept_cur;

EXCEPTION
    WHEN OTHERS THEN
        -- 예외 발생 시 커서 닫기 - ORA-06511 예방의 핵심
        IF dept_cur%ISOPEN THEN
            CLOSE dept_cur;
            DBMS_OUTPUT.PUT_LINE('커서를 안전하게 닫았습니다.');
        END IF;
        DBMS_OUTPUT.PUT_LINE('에러 발생: ' || SQLERRM);
        RAISE;
END;
/

원인 3 해결: 커서 FOR LOOP 사용으로 자동 관리

명시적 커서 관리의 복잡성을 피하고 싶다면, Oracle이 자동으로 커서를 관리해 주는 커서 FOR LOOP를 사용하는 것이 가장 안전합니다.

-- 방법 1: 명시적 커서를 FOR LOOP로 안전하게 사용
DECLARE
    CURSOR sal_cur IS
        SELECT employee_id, first_name, salary
        FROM employees
        WHERE salary > 5000;
BEGIN
    -- FOR LOOP는 내부적으로 OPEN, FETCH, CLOSE를 자동 처리
    FOR emp_rec IN sal_cur LOOP
        DBMS_OUTPUT.PUT_LINE(
            'ID: ' || emp_rec.employee_id ||
            ', Name: ' || emp_rec.first_name ||
            ', Salary: ' || emp_rec.salary
        );
    END LOOP;
    -- 별도의 CLOSE 불필요 (자동 처리됨)
END;
/

-- 방법 2: 인라인 쿼리를 사용하는 더 간결한 FOR LOOP
BEGIN
    FOR emp_rec IN (
        SELECT employee_id, first_name, salary
        FROM employees
        WHERE salary > 5000
        ORDER BY salary DESC
    ) LOOP
        DBMS_OUTPUT.PUT_LINE(
            'ID: ' || emp_rec.employee_id ||
            ', Name: ' || emp_rec.first_name
        );
    END LOOP;
END;
/

패키지 수준 커서 안전 관리 예제

패키지에서 전역 커서를 사용할 경우, 상태 관리가 특히 중요합니다.

CREATE OR REPLACE PACKAGE emp_pkg AS
    PROCEDURE process_employees(p_dept_id IN NUMBER);
END emp_pkg;
/

CREATE OR REPLACE PACKAGE BODY emp_pkg AS
    -- 패키지 수준 커서 선언
    CURSOR g_emp_cur IS
        SELECT employee_id, first_name, salary
        FROM employees;

    PROCEDURE process_employees(p_dept_id IN NUMBER) IS
    BEGIN
        -- 패키지 커서는 세션 내내 상태가 유지되므로 반드시 체크
        IF g_emp_cur%ISOPEN THEN
            CLOSE g_emp_cur;
        END IF;

        OPEN g_emp_cur;

        LOOP
            -- 처리 로직
            EXIT WHEN g_emp_cur%NOTFOUND;
        END LOOP;

        CLOSE g_emp_cur;

    EXCEPTION
        WHEN OTHERS THEN
            IF g_emp_cur%ISOPEN THEN
                CLOSE g_emp_cur;
            END IF;
            RAISE;
    END process_employees;

END emp_pkg;
/

예방 방법

  • 커서 FOR LOOP를 기본 패턴으로 채택하기

가능한 모든 경우에 커서 FOR LOOP(또는 인라인 SELECT FOR LOOP)를 사용하면, Oracle이 커서의 OPEN/FETCH/CLOSE를 자동으로 관리하므로 ORA-06511이 원천적으로 발생하지 않습니다. 명시적 커서는 BULK COLLECT나 REF CURSOR처럼 꼭 필요한 경우에만 제한적으로 사용하고, 사용 시에는 반드시 %ISOPEN 체크와 EXCEPTION 블록 내 CLOSE 처리를 표준 템플릿으로 정하여 팀 전체 코딩 컨벤션으로 적용하십시오.

  • 모든 PL/SQL 블록에서 EXCEPTION 절 내 커서 정리를 의무화하기

팀 코딩 표준에 “커서를 사용하는 모든 PL/SQL 블록의 EXCEPTION 절에는 반드시 IF cursor%ISOPEN THEN CLOSE cursor; END IF; 패턴을 포함한다”는 규칙을 명문화하십시오. 코드 리뷰 체크리스트에 이 항목을 추가하고, 정적 분석 도구(PL/SQL Cop, SQL Developer의 Code Analysis 등)를 CI/CD 파이프라인에 통합하여 커서 미닫힘 패턴을 자동으로 탐지하도록 구성하면 실수를 사전에 방지할 수 있습니다.


관련 에러

  • ORA-01001: invalid cursor — 유효하지 않은 커서 참조 시 발생. 아직 OPEN되지 않은 커서에 FETCH나 CLOSE를 시도할 때 나타나며 ORA-06511과 반대 상황입니다.
  • ORA-01002: fetch out of sequence — 커서가 닫힌 후 또는 마지막 레코드 이후에 FETCH를 시도할 때 발생합니다.
  • ORA-06500: PL/SQL: storage error — 커서 관련 메모리 할당 실패 시 발생하며, 커서 누수가 장시간 지속될 경우 간접적으로 연관될 수 있습니다.
  • ORA-04031: unable to allocate shared memory — 커서가 닫히지 않고 과도하게 누적될 경우 Shared Pool 메모리 부족으로 이어질 수 있습니다.

DBMS 에러 코드 시리즈

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

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

댓글 남기기