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

ORA-01410
2026년 07월 12일 | DBMS Error 가이드

이 글에서 다루는 내용

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

ORA-01410 invalid ROWID 는?

ORA-01410 에러는 Oracle 데이터베이스에서 유효하지 않은 ROWID를 사용하려 할 때 발생하는 오류입니다. ROWID는 Oracle 테이블에서 각 행(Row)의 물리적 위치를 나타내는 고유 식별자인데, 이 값이 손상되었거나 더 이상 존재하지 않는 행을 가리킬 때 이 에러가 트리거됩니다. 주로 잘못된 문자열을 ROWID로 변환하거나, 삭제된 행의 ROWID를 재사용하려 할 때, 또는 파티션 이동 후 ROWID가 무효화된 경우에 자주 목격됩니다.


주요 발생 원인

  • 잘못된 문자열을 ROWID로 강제 변환 (CHARTOROWID 오용)

가장 흔한 원인 중 하나로, 개발자가 일반 문자열 또는 잘못된 형식의 값을 CHARTOROWID() 함수나 명시적 캐스팅을 통해 ROWID로 변환하려 할 때 발생합니다. Oracle의 ROWID는 AAAE8NAAFAAAACXAAA와 같은 특정 Base64 인코딩 형식을 따르며, 이 형식에 맞지 않는 값을 변환하면 ORA-01410이 즉시 발생합니다. 예를 들어 외부 시스템에서 넘어온 임의의 문자열을 ROWID로 처리하는 코드에서 자주 나타납니다.

  • 파티션 테이블에서 행 이동(Row Movement) 후 ROWID 무효화

파티션 테이블에서 ENABLE ROW MOVEMENT 옵션이 설정된 상태에서 파티션 키 값이 변경되면, 해당 행은 다른 파티션으로 이동하게 됩니다. 이때 기존에 캐시하거나 저장해 두었던 ROWID는 더 이상 유효하지 않게 되며, 이를 다시 사용하려 할 때 ORA-01410이 발생합니다. 특히 장기 실행 커서(Long-running cursor)나 애플리케이션 내부에서 ROWID를 세션 간에 보존하는 경우 이 문제가 빈번히 발생합니다.

  • 삭제된 행의 ROWID를 이후에 재사용 시도

트랜잭션 도중 특정 행을 삭제한 뒤, 커밋 이전 또는 이후에 해당 ROWID를 다시 참조하는 경우에도 에러가 발생할 수 있습니다. 특히 FOR UPDATE 커서를 사용하는 PL/SQL 블록에서 루프 도중 행을 삭제하고 이후에 해당 ROWID로 UPDATE를 시도하는 패턴이 대표적입니다. 또한 글로벌 임시 테이블(Global Temporary Table)이나 External Table에서도 세션 종료 후 ROWID가 무효화되는 사례가 보고됩니다.


해결 방법

원인 1: 잘못된 CHARTOROWID 변환 수정

문자열을 ROWID로 변환하기 전에 형식을 반드시 검증해야 합니다. 아래는 유효성 검사 없이 변환하다 발생하는 에러와 이를 예외 처리로 안전하게 우회하는 방법입니다.

-- 잘못된 예: 형식이 맞지 않는 문자열을 ROWID로 변환
SELECT * FROM employees WHERE ROWID = CHARTOROWID('INVALID_STRING');
-- → ORA-01410 발생

-- 올바른 예: 예외 처리를 통한 안전한 ROWID 변환
DECLARE
  v_rowid ROWID;
  v_str   VARCHAR2(100) := 'AAAE8NAAFAAAACXAAA';
BEGIN
  BEGIN
    v_rowid := CHARTOROWID(v_str);
    -- ROWID를 사용하는 로직
    SELECT employee_id INTO :emp_id FROM employees WHERE ROWID = v_rowid;
  EXCEPTION
    WHEN OTHERS THEN
      IF SQLCODE = -1410 THEN
        DBMS_OUTPUT.PUT_LINE('유효하지 않은 ROWID입니다: ' || v_str);
      ELSE
        RAISE;
      END IF;
  END;
END;
/

-- ROWID 형식 사전 검증 (정규식 활용)
SELECT CASE
         WHEN REGEXP_LIKE(v_str, '^[A-Za-z0-9+/]{18}$') THEN '유효한 형식'
         ELSE '유효하지 않은 형식'
       END AS rowid_check
FROM dual;

원인 2: 파티션 테이블의 ROWID 무효화 해결

파티션 테이블을 다룰 때는 ROWID를 직접 저장하지 않고, 기본 키(Primary Key)를 통해 행을 식별하는 방식으로 전환해야 합니다.

-- 파티션 테이블 ROW MOVEMENT 확인
SELECT table_name, row_movement
FROM user_tables
WHERE table_name = 'SALES';

-- ROWID 대신 기본 키를 활용한 안전한 업데이트
-- 잘못된 방법: ROWID를 저장해 두고 나중에 사용
DECLARE
  v_rowid ROWID;
BEGIN
  SELECT ROWID INTO v_rowid FROM sales WHERE sale_id = 1001;
  -- ... 다른 처리 ...
  UPDATE sales SET amount = 500 WHERE ROWID = v_rowid; -- 위험!
END;
/

-- 올바른 방법: 기본 키로 직접 식별
BEGIN
  UPDATE sales SET amount = 500 WHERE sale_id = 1001; -- 안전
  COMMIT;
END;
/

-- 파티션 이동 가능성이 있는 테이블에서의 현재 ROWID 조회
SELECT sale_id, ROWIDTOCHAR(ROWID) AS current_rowid, sale_date, amount
FROM sales
WHERE sale_id = 1001;

원인 3: FOR UPDATE 커서에서 삭제된 행 참조 방지

-- 문제가 있는 패턴: 커서 루프 내 삭제 후 ROWID 재참조
DECLARE
  CURSOR c_emp IS
    SELECT ROWID, employee_id, salary FROM employees FOR UPDATE;
BEGIN
  FOR r IN c_emp LOOP
    IF r.salary < 3000 THEN
      DELETE FROM employees WHERE ROWID = r.ROWID;
    ELSE
      -- 이미 삭제된 행에 대해 WHERE CURRENT OF 사용 시 문제 발생 가능
      UPDATE employees SET salary = r.salary * 1.1
      WHERE CURRENT OF c_emp;
    END IF;
  END LOOP;
  COMMIT;
END;
/

-- 안전한 패턴: 삭제 대상과 수정 대상을 분리 처리
DECLARE
  TYPE t_del_ids IS TABLE OF NUMBER;
  TYPE t_upd_ids IS TABLE OF NUMBER;
  v_del_ids t_del_ids := t_del_ids();
  v_upd_ids t_upd_ids := t_upd_ids();
BEGIN
  -- 1단계: 대상 수집
  SELECT employee_id
  BULK COLLECT INTO v_del_ids
  FROM employees WHERE salary < 3000;

  SELECT employee_id
  BULK COLLECT INTO v_upd_ids
  FROM employees WHERE salary >= 3000;

  -- 2단계: 삭제 처리
  FORALL i IN 1..v_del_ids.COUNT
    DELETE FROM employees WHERE employee_id = v_del_ids(i);

  -- 3단계: 수정 처리
  FORALL i IN 1..v_upd_ids.COUNT
    UPDATE employees SET salary = salary * 1.1
    WHERE employee_id = v_upd_ids(i);

  COMMIT;
END;
/

예방 방법

  • ROWID를 장기 저장 키로 절대 사용하지 말 것

ROWID는 행의 물리적 위치를 나타내는 일시적 식별자이므로, 테이블이나 변수에 ROWID를 장기간 저장하여 나중에 재사용하는 패턴은 반드시 지양해야 합니다. 파티션 재구성, ALTER TABLE MOVE, SHRINK SPACE, EXPORT/IMPORT 등의 작업은 모두 ROWID를 변경시킬 수 있습니다. 항상 기본 키(Primary Key) 또는 유니크 인덱스 컬럼을 통해 행을 식별하고, ROWID는 동일 SQL 문장 또는 트랜잭션 범위 내에서만 사용하는 것을 원칙으로 삼으세요.

  • 파티션 테이블 접근 시 ROW MOVEMENT 설정을 사전에 파악하고 코드 설계에 반영

파티션 테이블을 사용하는 애플리케이션을 개발하거나 유지보수할 때는 DBA_TABLES.ROW_MOVEMENT 컬럼을 통해 해당 테이블의 Row Movement 활성화 여부를 사전에 확인해야 합니다. Row Movement가 ENABLED된 테이블에서는 DML 이후 ROWID가 변경될 수 있으므로, 해당 테이블을 다루는 모든 코드는 ROWID 대신 논리적 기본 키 기반으로 설계되어야 합니다. 코드 리뷰 단계에서 ROWID를 변수에 저장하거나 세션 간 전달하는 패턴이 발견되면 즉시 수정하는 프로세스를 팀 내 표준으로 정립하는 것이 좋습니다.


관련 에러

  • ORA-08006: 이미 삭제된 행을 참조할 때 발생하며, ORA-01410과 함께 나타나는 경우가 많습니다. SELECT FOR UPDATE 도중 다른 세션이 해당 행을 삭제했을 때 주로 발생합니다.
  • ORA-01445: 조인 뷰(Join View) 또는 DISTINCT, GROUP BY가 포함된 뷰에서 ROWID를 선택할 수 없을 때 발생합니다. ROWID 기반 작업을 뷰에서 시도할 때 함께 검토해야 할 에러입니다.
  • ORA-00060: 교착 상태(Deadlock) 발생 에러로, 여러 세션이 ROWID 기반으로 동일 행에 동시 접근 시도할 때 ORA-01410과 복합적으로 발생할 수 있습니다.
  • ORA-14402: 파티션 키 컬럼을 업데이트하려 할 때 ROW MOVEMENT가 비활성화된 경우 발생하는 에러로, ORA-01410의 파티션 관련 원인과 밀접하게 연관됩니다.

DBMS 에러 코드 시리즈

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

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

댓글 남기기