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

ORA-06533
2026년 08월 31일 | DBMS Error 가이드

이 글에서 다루는 내용

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

ORA-06533 Subscript beyond count 는?

ORA-06533 에러는 Oracle PL/SQL에서 컬렉션(Collection) 타입인 VARRAY 또는 Nested Table의 현재 요소 개수(COUNT)를 초과하는 인덱스로 접근하려 할 때 발생하는 에러입니다. 쉽게 말해, 배열에 실제로 존재하는 요소보다 더 큰 인덱스 번호로 데이터를 읽거나 쓰려 할 때 Oracle이 이를 감지하고 예외를 발생시키는 것입니다. 이 에러는 주로 동적으로 컬렉션 크기가 변하는 로직에서 인덱스 범위를 정확히 관리하지 않을 때 빈번하게 발생하며, 데이터 처리 배치 프로그램이나 복잡한 PL/SQL 패키지에서 자주 목격됩니다.


주요 발생 원인

1. 초기화되지 않거나 요소가 추가되지 않은 컬렉션에 인덱스 접근

컬렉션을 선언만 하고 실제로 EXTEND 메서드를 사용해 요소 공간을 확보하지 않은 채 인덱스로 접근하면 이 에러가 발생합니다. 예를 들어 VARRAY나 Nested Table을 초기화(생성자 호출)했더라도 요소가 하나도 없는 상태에서 my_array(1) := 'value' 와 같이 직접 접근하면 ORA-06533이 발생합니다. 실무에서는 루프를 통해 동적으로 데이터를 추가할 때 EXTEND 호출을 빠뜨리는 경우가 특히 많습니다.

2. 루프 또는 반복 처리에서 인덱스 범위를 잘못 계산

FOR 루프나 WHILE 루프에서 컬렉션의 COUNT보다 큰 인덱스까지 반복을 수행하도록 범위를 잘못 설정하면 에러가 발생합니다. 예를 들어 컬렉션에는 5개의 요소가 있는데, 루프의 상한(Upper Bound)을 하드코딩하거나 잘못된 변수로 설정해 6번 이상 반복하면 마지막 인덱스에서 ORA-06533이 터집니다. 이 경우는 코드 리뷰 없이 다른 컬렉션의 크기 변수를 재사용하다가 발생하는 케이스가 많습니다.

3. DELETE 후 인덱스를 재사용하거나 COUNT 값을 신뢰하는 로직

Nested Table은 중간 요소를 DELETE 메서드로 삭제할 수 있는데, 삭제 후에도 COUNT는 줄어든 개수를 반환하지만 인덱스가 재정렬되지 않습니다. 예를 들어 3개의 요소 중 2번 인덱스를 삭제하면 COUNT는 2가 되지만, 인덱스 1과 3만 존재하고 2는 비어있습니다. 이 상태에서 1..collection.COUNT 범위로 루프를 돌면, 존재하지 않는 인덱스에 접근하거나 실제로는 COUNT를 벗어나지 않더라도 논리 오류가 생겨 ORA-06533과 함께 ORA-06532 같은 관련 에러로 이어질 수 있습니다.


해결 방법

원인 1 해결: EXTEND를 사용하여 요소 공간 먼저 확보

컬렉션에 요소를 추가하기 전 반드시 EXTEND를 호출하여 공간을 먼저 할당해야 합니다.

DECLARE
  -- Nested Table 타입 선언
  TYPE num_table IS TABLE OF NUMBER;
  v_nums num_table := num_table(); -- 빈 컬렉션으로 초기화

BEGIN
  -- 잘못된 방법: EXTEND 없이 바로 접근 (ORA-06533 발생)
  -- v_nums(1) := 100;

  -- 올바른 방법: EXTEND로 공간 확보 후 값 할당
  v_nums.EXTEND(3); -- 3개 요소 공간 확보
  v_nums(1) := 100;
  v_nums(2) := 200;
  v_nums(3) := 300;

  FOR i IN 1..v_nums.COUNT LOOP
    DBMS_OUTPUT.PUT_LINE('v_nums(' || i || ') = ' || v_nums(i));
  END LOOP;
END;
/

동적으로 요소를 추가하는 경우에는 루프 내에서 매 반복마다 EXTEND를 호출하는 패턴을 사용합니다.

DECLARE
  TYPE str_table IS TABLE OF VARCHAR2(100);
  v_names str_table := str_table();
  
  CURSOR c_emp IS
    SELECT first_name FROM employees WHERE rownum <= 5;
BEGIN
  FOR rec IN c_emp LOOP
    v_names.EXTEND;                     -- 요소 1개 공간 확보
    v_names(v_names.LAST) := rec.first_name; -- LAST로 안전하게 접근
  END LOOP;

  FOR i IN 1..v_names.COUNT LOOP
    DBMS_OUTPUT.PUT_LINE(v_names(i));
  END LOOP;
END;
/

원인 2 해결: 루프 범위를 컬렉션의 FIRST와 LAST로 지정

하드코딩된 숫자 대신 컬렉션이 제공하는 FIRST, LAST, COUNT 속성을 활용합니다.

DECLARE
  TYPE salary_table IS TABLE OF NUMBER;
  v_salaries salary_table := salary_table(1000, 2000, 3000, 4000, 5000);
  v_total    NUMBER := 0;
BEGIN
  -- 잘못된 방법: 하드코딩된 범위 (데이터 변경 시 ORA-06533 위험)
  -- FOR i IN 1..10 LOOP
  --   v_total := v_total + v_salaries(i);
  -- END LOOP;

  -- 올바른 방법: FIRST..LAST 사용
  IF v_salaries IS NOT NULL AND v_salaries.COUNT > 0 THEN
    FOR i IN v_salaries.FIRST..v_salaries.LAST LOOP
      -- EXISTS로 요소 존재 여부 확인 (DELETE 후 희소 배열 대비)
      IF v_salaries.EXISTS(i) THEN
        v_total := v_total + v_salaries(i);
      END IF;
    END LOOP;
  END IF;

  DBMS_OUTPUT.PUT_LINE('Total Salary: ' || v_total);
END;
/

원인 3 해결: DELETE 후에는 EXISTS 검사 및 인덱스 재구성

Nested Table에서 삭제 후 안전하게 순회하는 방법입니다.

DECLARE
  TYPE product_table IS TABLE OF VARCHAR2(50);
  v_products product_table := product_table('Apple', 'Banana', 'Cherry', 'Durian', 'Elderberry');
  v_idx      PLS_INTEGER;
BEGIN
  -- 중간 요소 삭제 (인덱스 2, 4 삭제)
  v_products.DELETE(2);
  v_products.DELETE(4);

  DBMS_OUTPUT.PUT_LINE('COUNT after DELETE: ' || v_products.COUNT);

  -- 잘못된 방법: 1..COUNT 루프 (희소 인덱스로 ORA-06533/ORA-06532 위험)
  -- FOR i IN 1..v_products.COUNT LOOP ...

  -- 올바른 방법: FIRST/NEXT를 이용한 희소 배열 순회
  v_idx := v_products.FIRST;
  WHILE v_idx IS NOT NULL LOOP
    DBMS_OUTPUT.PUT_LINE('Index ' || v_idx || ': ' || v_products(v_idx));
    v_idx := v_products.NEXT(v_idx);
  END LOOP;
END;
/

예외 처리로 에러 포착 및 로깅

운영 환경에서는 예외 처리를 추가해 에러 발생 시 원인을 파악할 수 있도록 합니다.

DECLARE
  TYPE num_list IS TABLE OF NUMBER;
  v_data num_list := num_list(10, 20, 30);
  v_val  NUMBER;
BEGIN
  -- 의도적으로 범위 초과 접근 시도
  FOR i IN 1..5 LOOP
    BEGIN
      v_val := v_data(i);
      DBMS_OUTPUT.PUT_LINE('Index ' || i || ': ' || v_val);
    EXCEPTION
      WHEN SUBSCRIPT_BEYOND_COUNT THEN
        DBMS_OUTPUT.PUT_LINE('[경고] 인덱스 ' || i || '는 컬렉션 범위를 초과합니다. COUNT=' || v_data.COUNT);
      WHEN SUBSCRIPT_OUTSIDE_LIMIT THEN
        DBMS_OUTPUT.PUT_LINE('[경고] 인덱스 ' || i || '는 허용 범위를 벗어납니다.');
    END;
  END LOOP;
END;
/

예방 방법

1. 컬렉션 접근 전 항상 COUNT > 0 확인 및 EXISTS 메서드 활용

코딩 표준으로 컬렉션에 접근하기 전에 반드시 컬렉션.COUNT > 0 조건을 확인하는 가드(Guard) 코드를 작성하도록 팀 전체에 규칙을 정립하세요. 특히 Nested Table처럼 중간 삭제가 가능한 컬렉션을 순회할 때는 EXISTS(i) 체크를 루프 내부에 포함시켜, 희소 배열(Sparse Array)로 인한 잘못된 접근을 원천 차단하는 습관을 들이십시오. 또한 FORALL 구문 사용 시에도 INDICES OF 또는 VALUES OF 절을 적극 활용하면 더욱 안전합니다.

2. 루프 범위는 반드시 FIRST, LAST, COUNT 속성을 사용하고 단위 테스트 작성

컬렉션 크기를 하드코딩하지 말고 항상 collection.FIRST, collection.LAST, collection.COUNT 속성을 사용하여 루프 범위를 동적으로 결정하세요. 나아가 PL/SQL 로직에 대한 단위 테스트(utPLSQL 등 활용)를 작성할 때, 빈 컬렉션, 요소 1개짜리 컬렉션, 최대 크기 컬렉션, 중간 요소가 삭제된 희소 컬렉션 등 경계값(Boundary Value) 케이스를 반드시 테스트 케이스로 포함시키면 운영 배포 전에 이 류의 에러를 대부분 잡아낼 수 있습니다.


관련 에러

  • ORA-06531 (Reference to uninitialized collection): 컬렉션 자체가 NULL인 상태, 즉 생성자조차 호출하지 않은 컬렉션에 접근할 때 발생합니다. ORA-06533보다 한 단계 앞선 문제로, 초기화 여부를 먼저 점검해야 합니다.
  • ORA-06532 (Subscript outside of limit): VARRAY의 최대 크기(선언 시 지정한 한도)를 초과하는 인덱스로 접근할 때 발생합니다. ORA-06533이 현재 COUNT 초과라면, ORA-06532는 선언된 최대 한도 초과라는 점에서 구분됩니다.
  • ORA-01403 (No data found): 커서나 SELECT INTO에서 데이터가 없을 때 발생하며, 컬렉션에 데이터를 담는 과정에서 함께 처리해야 하는 경우가 많습니다.

DBMS 에러 코드 시리즈

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

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

댓글 남기기