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

ORA-06504
2026년 08월 28일 | DBMS Error 가이드

이 글에서 다루는 내용

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

ORA-06504 PL/SQL: return types of result set variables do not match 는?

ORA-06504는 PL/SQL에서 커서 변수(REF CURSOR)를 사용할 때, 반환되는 결과 집합(Result Set)의 타입이 서로 일치하지 않을 경우 발생하는 에러입니다. 주로 강타입(Strong Type) REF CURSOR를 선언하고 사용할 때, 선언된 타입과 실제 반환되는 쿼리의 컬럼 구조가 맞지 않으면 Oracle 엔진이 이 에러를 발생시킵니다. 실무에서는 프로시저 간 커서를 주고받거나, 동적 SQL과 함께 커서를 사용하는 복잡한 코드에서 자주 접하게 되는 에러입니다.


주요 발생 원인

1. 강타입 REF CURSOR의 타입 불일치

강타입(Strong Typed) REF CURSOR는 선언 시 반환 타입을 명시적으로 지정합니다. 이때 커서 변수에 OPEN FOR 구문으로 여는 쿼리의 컬럼 수, 데이터 타입, 순서 중 하나라도 다르면 ORA-06504가 발생합니다. 예를 들어 RETURN employees%ROWTYPE으로 선언된 커서에 departments 테이블을 조회하는 쿼리를 연결하면 반드시 에러가 납니다.

2. 프로시저 간 REF CURSOR 전달 시 타입 정의 불일치

하나의 프로시저에서 생성한 커서 변수를 다른 프로시저나 함수에 OUT 파라미터로 전달할 때, 송신 측과 수신 측이 서로 다른 타입으로 커서를 정의한 경우 에러가 발생합니다. 이는 패키지 간 인터페이스가 명확히 정의되지 않은 레거시 코드에서 특히 자주 나타납니다. 각 패키지가 독립적으로 커서 타입을 선언하다 보면 컬럼 수나 타입이 미묘하게 달라지는 경우가 생깁니다.

3. 동적 SQL(OPEN FOR … USING)에서의 타입 불일치

동적 SQL을 사용하여 REF CURSOR를 열 때, 런타임에 결정되는 쿼리의 결과 구조가 강타입 커서의 선언 타입과 맞지 않으면 에러가 발생합니다. 개발 단계에서는 문제가 없다가, 운영 중에 테이블 구조가 변경되거나 동적으로 다른 쿼리가 주입되면서 뒤늦게 에러가 발생하는 경우가 많습니다. 이런 유형은 디버깅이 어렵기 때문에 각별한 주의가 필요합니다.


해결 방법

원인 1 해결: 커서 타입을 정확히 일치시키기

커서 변수의 반환 타입과 실제 쿼리의 결과 컬럼이 일치하도록 수정합니다.

-- 잘못된 예시: 타입 불일치로 ORA-06504 발생
DECLARE
    TYPE emp_cursor_type IS REF CURSOR RETURN employees%ROWTYPE;
    v_cursor emp_cursor_type;
BEGIN
    -- departments 테이블을 열려고 하면 타입 불일치 에러 발생
    OPEN v_cursor FOR SELECT * FROM departments; -- ORA-06504 발생
END;
/

-- 올바른 예시 1: 커서 타입과 쿼리를 일치시킴
DECLARE
    TYPE emp_cursor_type IS REF CURSOR RETURN employees%ROWTYPE;
    v_cursor emp_cursor_type;
    v_row    employees%ROWTYPE;
BEGIN
    OPEN v_cursor FOR SELECT * FROM employees; -- 타입 일치
    FETCH v_cursor INTO v_row;
    DBMS_OUTPUT.PUT_LINE('Employee Name: ' || v_row.first_name);
    CLOSE v_cursor;
END;
/

-- 올바른 예시 2: 별도 레코드 타입을 정의하여 사용
DECLARE
    TYPE dept_rec_type IS RECORD (
        dept_id   departments.department_id%TYPE,
        dept_name departments.department_name%TYPE
    );
    TYPE dept_cursor_type IS REF CURSOR RETURN dept_rec_type;
    v_cursor dept_cursor_type;
    v_row    dept_rec_type;
BEGIN
    OPEN v_cursor FOR
        SELECT department_id, department_name FROM departments;
    LOOP
        FETCH v_cursor INTO v_row;
        EXIT WHEN v_cursor%NOTFOUND;
        DBMS_OUTPUT.PUT_LINE(v_row.dept_id || ' - ' || v_row.dept_name);
    END LOOP;
    CLOSE v_cursor;
END;
/

원인 2 해결: 패키지에서 공통 커서 타입 정의 및 공유

여러 패키지 간 커서를 주고받을 때는 공통 패키지에 커서 타입을 단 한 번만 정의하고 참조하도록 합니다.

-- 공통 타입 패키지 정의
CREATE OR REPLACE PACKAGE common_types_pkg AS
    TYPE emp_ref_cursor IS REF CURSOR RETURN employees%ROWTYPE;
END common_types_pkg;
/

-- 커서를 생성하는 패키지
CREATE OR REPLACE PACKAGE producer_pkg AS
    PROCEDURE get_employees(p_cursor OUT common_types_pkg.emp_ref_cursor);
END producer_pkg;
/

CREATE OR REPLACE PACKAGE BODY producer_pkg AS
    PROCEDURE get_employees(p_cursor OUT common_types_pkg.emp_ref_cursor) IS
    BEGIN
        OPEN p_cursor FOR SELECT * FROM employees;
    END get_employees;
END producer_pkg;
/

-- 커서를 소비하는 패키지
CREATE OR REPLACE PACKAGE consumer_pkg AS
    PROCEDURE process_employees;
END consumer_pkg;
/

CREATE OR REPLACE PACKAGE BODY consumer_pkg AS
    PROCEDURE process_employees IS
        v_cursor common_types_pkg.emp_ref_cursor; -- 동일한 타입 참조
        v_row    employees%ROWTYPE;
    BEGIN
        producer_pkg.get_employees(v_cursor);
        LOOP
            FETCH v_cursor INTO v_row;
            EXIT WHEN v_cursor%NOTFOUND;
            DBMS_OUTPUT.PUT_LINE('Processing: ' || v_row.first_name || ' ' || v_row.last_name);
        END LOOP;
        CLOSE v_cursor;
    END process_employees;
END consumer_pkg;
/

-- 실행 테스트
EXEC consumer_pkg.process_employees;

원인 3 해결: 동적 SQL 사용 시 약타입 커서로 전환 또는 타입 검증 추가

동적 SQL처럼 런타임에 쿼리가 결정되는 경우에는 약타입(Weak Type) REF CURSOR인 SYS_REFCURSOR를 사용하는 것이 안전합니다.

-- 동적 SQL에서 SYS_REFCURSOR 활용 (권장)
CREATE OR REPLACE PROCEDURE get_data_dynamic(
    p_table_name IN  VARCHAR2,
    p_cursor     OUT SYS_REFCURSOR
) AS
    v_sql VARCHAR2(1000);
BEGIN
    -- SQL Injection 방지를 위해 테이블명 검증
    IF p_table_name NOT IN ('EMPLOYEES', 'DEPARTMENTS', 'JOBS') THEN
        RAISE_APPLICATION_ERROR(-20001, '허용되지 않은 테이블명입니다: ' || p_table_name);
    END IF;

    v_sql := 'SELECT * FROM ' || DBMS_ASSERT.SQL_OBJECT_NAME(p_table_name);
    OPEN p_cursor FOR v_sql;
EXCEPTION
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('에러 발생: ' || SQLERRM);
        RAISE;
END get_data_dynamic;
/

-- 호출 예시
DECLARE
    v_cursor SYS_REFCURSOR;
    v_emp    employees%ROWTYPE;
BEGIN
    get_data_dynamic('EMPLOYEES', v_cursor);
    FETCH v_cursor INTO v_emp;
    DBMS_OUTPUT.PUT_LINE('첫 번째 직원: ' || v_emp.first_name);
    CLOSE v_cursor;
END;
/

예방 방법

1. 공통 패키지에서 커서 타입을 중앙 집중식으로 관리하라

프로젝트 전체에서 사용하는 REF CURSOR 타입을 하나의 공통 패키지(예: COMMON_TYPES_PKG)에 선언하고, 모든 패키지와 프로시저가 해당 타입을 참조하도록 표준화합니다. 이렇게 하면 타입 불일치 가능성을 원천 차단할 수 있으며, 향후 타입 변경 시에도 한 곳만 수정하면 되므로 유지보수성이 크게 향상됩니다. 팀 코딩 표준 문서에 이 규칙을 명문화하고 코드 리뷰 시 반드시 점검하는 것을 권장합니다.

2. 강타입과 약타입 커서의 사용 기준을 명확히 정의하라

컴파일 시점에 쿼리 구조가 확정되는 정적 SQL에는 강타입 REF CURSOR를, 런타임에 쿼리가 결정되는 동적 SQL에는 약타입인 SYS_REFCURSOR를 일관되게 사용하도록 팀 규칙을 수립합니다. 강타입 커서는 타입 안정성을 보장하지만 유연성이 낮고, 약타입 커서는 유연하지만 타입 오류를 런타임에야 발견하는 단점이 있습니다. 따라서 사용 상황에 맞는 기준을 문서화하고, 단위 테스트를 통해 커서 타입 호환성을 검증하는 습관을 들이는 것이 중요합니다.


관련 에러

  • ORA-06511: PL/SQL: cursor already open — 이미 열린 커서를 다시 열려고 할 때 발생하며, 커서 관리 로직 점검이 필요합니다.
  • ORA-01001: invalid cursor — 유효하지 않은 커서 핸들을 사용할 때 발생하며, 커서가 정상적으로 OPEN 되었는지 확인해야 합니다.
  • ORA-06502: PL/SQL: numeric or value error — 커서로 FETCH한 데이터를 변수에 담을 때 타입 변환 오류로 발생할 수 있으며, ORA-06504와 함께 나타나는 경우도 있습니다.
  • ORA-00932: inconsistent datatypes — SQL 레벨에서의 데이터 타입 불일치 에러로, REF CURSOR의 반환 타입 문제와 맥락이 유사합니다.
DBMS 에러 코드 시리즈

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

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

댓글 남기기