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 error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.