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

ORA-00069
2026년 10월 24일 | DBMS Error 가이드

이 글에서 다루는 내용

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

ORA-00069 cannot acquire lock – table locks disabled 는?

ORA-00069 에러는 Oracle 데이터베이스에서 특정 테이블에 대한 잠금(Lock)을 획득하려 할 때, 해당 테이블의 테이블 잠금(Table Lock, TM Lock)이 비활성화되어 있을 경우 발생하는 에러입니다. 주로 LOCK TABLE, SELECT ... FOR UPDATE, DDL 작업(예: ALTER TABLE) 등의 구문을 실행할 때 나타나며, Oracle이 내부적으로 TM 잠금을 확보하지 못할 때 트리거됩니다. 이 에러는 성능 향상을 위해 의도적으로 테이블 잠금을 비활성화한 환경(특히 병렬 DML 또는 Direct Path Insert를 많이 사용하는 시스템)에서 자주 발생합니다.


주요 발생 원인

  • DISABLE TABLE LOCK 명령으로 테이블 잠금이 명시적으로 비활성화된 경우

가장 흔한 원인입니다. DBA나 개발자가 성능 최적화 또는 병렬 처리 목적으로 ALTER TABLE ... DISABLE TABLE LOCK 명령을 실행하여 테이블 잠금 기능을 꺼놓은 경우입니다. 이 상태에서 해당 테이블에 대해 DML(INSERT, UPDATE, DELETE), DDL, 또는 명시적 LOCK TABLE 구문을 실행하면 ORA-00069가 발생합니다. 특히 운영 환경에서 이 설정이 적용된 사실을 팀원들이 공유하지 않았을 때 예기치 않게 마주치는 경우가 많습니다.

  • Direct Path Insert(병렬 INSERT) 이후 일반 DML 시도

INSERT /+ APPEND / 힌트나 병렬 DML을 사용한 Direct Path Insert를 수행한 후, 같은 세션에서 커밋(COMMIT) 또는 롤백(ROLLBACK) 없이 동일 테이블에 일반 DML을 시도할 경우 발생할 수 있습니다. Direct Path Insert는 테이블 잠금 메커니즘을 다르게 활용하기 때문에, 잠금 상태가 충돌하여 ORA-00069로 이어질 수 있습니다. 이 원인은 배치 처리나 데이터 마이그레이션 스크립트에서 특히 자주 나타납니다.

  • 애플리케이션 또는 프레임워크의 의도치 않은 잠금 요청

일부 ORM(Object-Relational Mapping) 프레임워크나 레거시 애플리케이션이 내부적으로 SELECT ... FOR UPDATE 또는 명시적 LOCK TABLE 구문을 자동으로 실행하는 경우가 있습니다. 개발자는 이 사실을 인지하지 못한 채 테이블 잠금이 비활성화된 테이블을 대상으로 조회하다가 에러가 발생하는 상황을 겪게 됩니다. 특히 JPA/Hibernate의 비관적 락(Pessimistic Lock) 설정이나 Spring Batch의 메타테이블 접근 시 이런 문제가 생길 수 있습니다.


해결 방법

원인 1 해결: 테이블 잠금 재활성화

우선 현재 테이블의 잠금 상태를 확인합니다.

-- 테이블의 현재 잠금 상태 확인
SELECT table_name, row_movement, dependencies
FROM user_tables
WHERE table_name = 'YOUR_TABLE_NAME';

-- 테이블 잠금이 활성화되어 있는지 확인 (DBA_TABLES 뷰 활용)
SELECT owner, table_name
FROM dba_tables
WHERE table_name = 'YOUR_TABLE_NAME';

> 참고: dba_tables에서 직접 lock 상태를 조회하는 컬럼은 제공되지 않으므로, 잠금 비활성화 여부는 아래 명령으로 활성화 후 재시도하거나 DBA_SEGMENTS, V$ 뷰를 통해 간접 확인합니다.

테이블 잠금을 다시 활성화하려면 다음 명령을 실행합니다.

-- 테이블 잠금 활성화
ALTER TABLE your_table_name ENABLE TABLE LOCK;

-- 이후 정상적으로 DML 또는 잠금 획득 가능
LOCK TABLE your_table_name IN EXCLUSIVE MODE;

-- 작업 완료 후 커밋
COMMIT;

원인 2 해결: Direct Path Insert 후 커밋 처리

Direct Path Insert 이후에는 반드시 커밋 또는 롤백 후 다음 DML을 수행합니다.

-- 잘못된 패턴 (에러 발생 가능)
INSERT /*+ APPEND */ INTO your_table_name
SELECT * FROM source_table;

-- 커밋 없이 바로 UPDATE 시도 -> ORA-00069 발생 가능
UPDATE your_table_name SET column1 = 'value' WHERE id = 1;

-- ============================================
-- 올바른 패턴
INSERT /*+ APPEND */ INTO your_table_name
SELECT * FROM source_table;

-- 반드시 커밋 후 다음 DML 수행
COMMIT;

UPDATE your_table_name SET column1 = 'value' WHERE id = 1;
COMMIT;

원인 3 해결: 애플리케이션 측 잠금 요청 우회

애플리케이션 코드에서 비관적 락 사용을 중단하거나, 해당 테이블의 잠금을 활성화합니다.

-- 문제가 되는 구문 예시
LOCK TABLE your_table_name IN SHARE MODE;
-- 또는
SELECT * FROM your_table_name FOR UPDATE;

-- 해결 방법 1: 테이블 잠금 활성화
ALTER TABLE your_table_name ENABLE TABLE LOCK;

-- 해결 방법 2: FOR UPDATE 대신 낙관적 잠금 패턴 사용 (애플리케이션 레벨)
-- 버전 컬럼이나 타임스탬프를 활용한 낙관적 잠금 예시
SELECT id, data, last_modified
FROM your_table_name
WHERE id = :target_id;

-- 조회한 last_modified 값을 WHERE 조건에 포함하여 UPDATE
UPDATE your_table_name
SET data = :new_data,
    last_modified = SYSTIMESTAMP
WHERE id = :target_id
  AND last_modified = :original_last_modified;

-- 영향받은 행이 0이면 충돌 발생 -> 애플리케이션에서 처리
COMMIT;

현재 테이블 잠금 비활성화 목록 전체 조회

운영 환경에서 어떤 테이블들이 잠금 비활성화 상태인지 확인하는 쿼리입니다.

-- 잠금이 비활성화된 테이블 식별을 위한 조회
-- (Oracle 내부적으로 DISABLE TABLE LOCK 상태는 별도 메타테이블에 기록됨)
SELECT u.object_name AS table_name,
       u.object_type,
       u.status
FROM   user_objects u
WHERE  u.object_type = 'TABLE'
ORDER BY u.object_name;

-- DBA 권한이 있는 경우 전체 스키마 조회
SELECT owner, table_name, status
FROM   dba_objects
WHERE  object_type = 'TABLE'
  AND  status != 'VALID'
ORDER BY owner, table_name;

-- 현재 세션에서 발생한 잠금 대기 확인
SELECT s.sid,
       s.serial#,
       s.username,
       s.program,
       l.type,
       l.lmode,
       l.request,
       o.object_name
FROM   v$lock l
JOIN   v$session s ON l.sid = s.sid
JOIN   dba_objects o ON l.id1 = o.object_id
WHERE  l.request > 0
ORDER BY s.sid;

예방 방법

  • 테이블 잠금 비활성화 정책 문서화 및 변경 관리 적용

ALTER TABLE ... DISABLE TABLE LOCK은 강력한 영향을 미치는 명령입니다. 이를 실행하기 전에 반드시 해당 테이블을 사용하는 모든 애플리케이션과 배치 잡에 영향이 없는지 사전 검토해야 합니다. 변경 관리(Change Management) 프로세스에 이 명령을 포함시키고, 적용 후에는 팀 전체에 공유하며 운영 매뉴얼에 반드시 기록해두어야 합니다. 또한 정기적으로 잠금 비활성화된 테이블 목록을 점검하는 모니터링 스크립트를 스케줄러(DBMS_SCHEDULER)에 등록하여 자동으로 리포트를 생성하면 예방에 효과적입니다.

  • Direct Path Insert 사용 시 트랜잭션 경계 명확히 관리

배치 프로세스나 데이터 적재 작업에서 APPEND 힌트 또는 병렬 DML을 사용할 때는 반드시 각 작업 단위의 시작과 끝에서 COMMIT/ROLLBACK을 명확히 실행하는 코딩 규칙을 수립해야 합니다. 특히 하나의 프로시저나 스크립트 내에서 Direct Path Insert와 일반 DML을 혼용하는 경우, 실행 순서와 트랜잭션 경계를 명확히 주석으로 남기고 코드 리뷰를 통해 확인하는 절차를 의무화하는 것이 좋습니다.


관련 에러

  • ORA-00054: resource busy and acquire with NOWAIT specified or timeout expired — 테이블 잠금 자체는 활성화되어 있지만, 다른 세션이 이미 잠금을 보유하고 있어 획득하지 못할 때 발생합니다. ORA-00069와 달리 잠금 기능 자체의 비활성화가 아닌 경합(Contention) 문제입니다.
  • ORA-00055: too many enqueue resources — 시스템 전체의 잠금 리소스가 부족할 때 발생하며, ENQUEUE_RESOURCES 파라미터와 연관됩니다.
  • ORA-02049: timeout: distributed transaction waiting for lock — 분산 트랜잭션 환경에서 잠금 대기 시간이 초과될 때 발생하며, 테이블 잠금 관련 문제와 함께 나타날 수 있습니다.
  • ORA-00060: deadlock detected while waiting for resource — 두 세션이 서로의 잠금을 기다리며 교착 상태가 발생할 때 나타나며, 잠금 관리 전략 점검 시 함께 확인해야 합니다.

DBMS 에러 코드 시리즈

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

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

댓글 남기기