2026년 06월 26일 | DBMS Error 가이드
이 글에서 다루는 내용
ORA-01003 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.
ORA-01003 no statement parsed 는?
ORA-01003은 Oracle 데이터베이스에서 커서(Cursor)를 실행하거나 패치(Fetch)하려 할 때, 해당 커서에 아직 SQL 문이 파싱(Parse)되지 않은 상태에서 작업을 시도할 경우 발생하는 에러입니다. 쉽게 말해, SQL 문을 커서에 준비(Prepare/Parse)하지 않은 채로 EXECUTE나 FETCH를 호출했을 때 Oracle이 “파싱된 문장이 없다”고 알려주는 것입니다. 주로 Pro*C, OCI(Oracle Call Interface), JDBC, PL/SQL 동적 SQL 처리 코드에서 커서 라이프사이클 관리가 잘못되었을 때 자주 나타납니다.
주요 발생 원인
1. 커서 열기(OPEN) 전 FETCH 또는 EXECUTE 시도
가장 흔한 원인으로, 커서를 선언한 후 OPEN 없이 바로 FETCH를 시도하거나, 동적 SQL에서 PARSE 단계를 건너뛰고 EXECUTE를 호출하는 경우입니다. OCI나 Pro*C 환경에서는 커서 핸들을 할당받은 뒤 반드시 파싱 단계를 거쳐야 하며, 이 순서가 어긋나면 ORA-01003이 발생합니다. 특히 코드 리팩토링 과정에서 커서 초기화 로직이 누락되거나 조건 분기로 인해 파싱 단계가 스킵되는 경우가 많습니다.
2. 커서 재사용 시 파싱 없이 EXECUTE 호출
한 번 사용한 커서를 닫지 않고 재사용하거나, 닫은 커서를 다시 파싱하지 않고 바로 실행하려 할 때 발생합니다. 프로그램 내에서 동일한 커서 변수를 루프 안에서 반복 사용할 때 CLOSE 후 OPEN(또는 PARSE)을 다시 수행하지 않으면 두 번째 실행부터 이 에러가 나타날 수 있습니다. 특히 커넥션 풀링 환경에서 커서 상태가 초기화되지 않은 채 재사용되는 케이스가 실무에서 자주 보고됩니다.
3. PL/SQL 동적 SQL(DBMS_SQL)에서 잘못된 커서 핸들 사용
DBMS_SQL 패키지를 사용하는 동적 SQL 코드에서 커서 ID를 잘못 관리하거나, 이미 닫힌 커서 ID를 재사용하려 할 때 발생합니다. DBMS_SQL.OPEN_CURSOR로 커서를 열었더라도 DBMS_SQL.PARSE를 호출하지 않고 DBMS_SQL.EXECUTE를 수행하면 ORA-01003이 트리거됩니다. 복잡한 동적 SQL 로직에서 예외 처리 블록 안에서 커서가 비정상 종료된 이후 동일 커서 핸들을 재사용하는 패턴도 원인이 됩니다.
해결 방법
원인 1 해결: 커서 OPEN → PARSE → EXECUTE → FETCH 순서 준수
PL/SQL에서 동적 커서를 사용할 때는 아래와 같이 DBMS_SQL의 정확한 순서를 지켜야 합니다.
DECLARE
v_cursor INTEGER;
v_result INTEGER;
v_empno NUMBER;
v_ename VARCHAR2(50);
BEGIN
-- 1단계: 커서 열기
v_cursor := DBMS_SQL.OPEN_CURSOR;
-- 2단계: 반드시 PARSE 수행 (이 단계를 빠뜨리면 ORA-01003 발생)
DBMS_SQL.PARSE(
v_cursor,
'SELECT empno, ename FROM emp WHERE deptno = :deptno',
DBMS_SQL.NATIVE
);
-- 3단계: 바인드 변수 설정
DBMS_SQL.BIND_VARIABLE(v_cursor, ':deptno', 10);
-- 4단계: 컬럼 정의
DBMS_SQL.DEFINE_COLUMN(v_cursor, 1, v_empno);
DBMS_SQL.DEFINE_COLUMN(v_cursor, 2, v_ename, 50);
-- 5단계: 실행
v_result := DBMS_SQL.EXECUTE(v_cursor);
-- 6단계: FETCH
LOOP
EXIT WHEN DBMS_SQL.FETCH_ROWS(v_cursor) = 0;
DBMS_SQL.COLUMN_VALUE(v_cursor, 1, v_empno);
DBMS_SQL.COLUMN_VALUE(v_cursor, 2, v_ename);
DBMS_OUTPUT.PUT_LINE('EMPNO: ' || v_empno || ', ENAME: ' || v_ename);
END LOOP;
-- 7단계: 커서 닫기
DBMS_SQL.CLOSE_CURSOR(v_cursor);
EXCEPTION
WHEN OTHERS THEN
-- 예외 발생 시 커서 반드시 닫기
IF DBMS_SQL.IS_OPEN(v_cursor) THEN
DBMS_SQL.CLOSE_CURSOR(v_cursor);
END IF;
RAISE;
END;
/
원인 2 해결: 커서 재사용 시 올바른 재파싱
루프 내에서 커서를 재사용할 경우, 반드시 CLOSE 후 OPEN과 PARSE를 다시 수행하거나, 아래처럼 REF CURSOR를 활용하는 방식으로 전환하는 것이 안전합니다.
DECLARE
TYPE ref_cur IS REF CURSOR;
v_cur ref_cur;
v_ename VARCHAR2(50);
v_sal NUMBER;
TYPE dept_list IS TABLE OF NUMBER INDEX BY PLS_INTEGER;
v_depts dept_list;
BEGIN
-- 부서 목록 초기화
v_depts(1) := 10;
v_depts(2) := 20;
v_depts(3) := 30;
FOR i IN 1 .. v_depts.COUNT LOOP
-- 반복 시마다 커서를 새로 OPEN (재파싱 보장)
OPEN v_cur FOR
SELECT ename, sal
FROM emp
WHERE deptno = v_depts(i)
ORDER BY sal DESC;
DBMS_OUTPUT.PUT_LINE('=== DEPTNO: ' || v_depts(i) || ' ===');
LOOP
FETCH v_cur INTO v_ename, v_sal;
EXIT WHEN v_cur%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(v_ename || ' : ' || v_sal);
END LOOP;
-- 반드시 닫고 다음 반복으로
CLOSE v_cur;
END LOOP;
END;
/
원인 3 해결: DBMS_SQL 커서 핸들 안전하게 관리
예외 처리 후 커서 핸들의 유효성을 확인하고 재파싱하는 패턴을 적용합니다.
DECLARE
v_cursor INTEGER := -1; -- 초기값을 -1로 설정하여 미할당 상태 구분
v_ret INTEGER;
v_sql VARCHAR2(1000);
BEGIN
v_sql := 'UPDATE emp SET sal = sal * 1.1 WHERE deptno = :dept';
-- 커서가 열려있지 않을 때만 새로 열기
IF v_cursor = -1 OR NOT DBMS_SQL.IS_OPEN(v_cursor) THEN
v_cursor := DBMS_SQL.OPEN_CURSOR;
END IF;
-- 파싱 수행 (재사용 시에도 반드시 파싱)
DBMS_SQL.PARSE(v_cursor, v_sql, DBMS_SQL.NATIVE);
DBMS_SQL.BIND_VARIABLE(v_cursor, ':dept', 10);
v_ret := DBMS_SQL.EXECUTE(v_cursor);
DBMS_OUTPUT.PUT_LINE(v_ret || '건 업데이트 완료');
DBMS_SQL.CLOSE_CURSOR(v_cursor);
COMMIT;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
IF DBMS_SQL.IS_OPEN(v_cursor) THEN
DBMS_SQL.CLOSE_CURSOR(v_cursor);
END IF;
DBMS_OUTPUT.PUT_LINE('오류 발생: ' || SQLERRM);
RAISE;
END;
/
예방 방법
1. 커서 라이프사이클 표준 템플릿 적용
팀 내 PL/SQL 코딩 표준에 “OPEN → PARSE → BIND → DEFINE → EXECUTE → FETCH → CLOSE” 순서를 명시하고, 모든 동적 SQL 코드에 EXCEPTION 블록에서 DBMS_SQL.IS_OPEN() 체크 후 CLOSE_CURSOR를 수행하는 패턴을 코드 리뷰 체크리스트에 포함시키세요. 커서 핸들 변수는 항상 -1과 같은 명시적 초기값으로 선언하여 미초기화 상태를 쉽게 감지할 수 있도록 합니다.
2. 가능하면 DBMS_SQL 대신 Native Dynamic SQL(NDS) 사용
EXECUTE IMMEDIATE나 OPEN ... FOR 구문을 사용하는 Native Dynamic SQL은 커서 관리를 Oracle 엔진이 내부적으로 처리하므로, 파싱 순서 오류로 인한 ORA-01003 발생 위험이 현저히 줄어듭니다. 단, BULK COLLECT나 다중 바인드 변수가 필요한 복잡한 경우에만 DBMS_SQL을 선택적으로 사용하고, 그 외에는 NDS를 기본으로 채택하는 개발 가이드라인을 수립하는 것이 좋습니다.
관련 에러
- ORA-01000: maximum open cursors exceeded — 커서를 닫지 않고 계속 열 때 발생하며, ORA-01003과 함께 커서 관리 문제에서 동반 발생하는 경우가 많습니다.
- ORA-01001: invalid cursor — 유효하지 않은 커서 핸들에 접근할 때 발생하며, ORA-01003과 유사한 커서 상태 오류입니다.
- ORA-06502: PL/SQL: numeric or value error — DBMS_SQL 사용 중 커서 ID 타입 불일치 시 함께 발생할 수 있습니다.
- ORA-01002: fetch out of sequence — FETCH 순서가 맞지 않을 때 발생하며, 커서 라이프사이클 위반 에러군에 속합니다.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.