본문 바로가기
IT

ORA-02292 integrity constraint violated child record found — 자식 레코드 발견 에러 원인·해결·예방

by 샤나엘 2026. 5. 21.
반응형

ORA-02292는 부모 행을 삭제하거나 기본 키 값을 변경하려 할 때 그 행을 참조하는 자식 행이 남아 있을 때 발생한다. ORA-02291이 "자식이 가리킬 부모가 없다"고 막는다면, ORA-02292는 "이 부모를 지우면 자식이 고아가 된다"고 막는 정반대 방향의 무결성 방어선이다. 외래키 옵션 설계가 부족한 시스템에서는 운영 중 매우 자주 마주치며, FK 인덱스 누락과 결합되면 락 경합·데드락까지 유발한다. 본 글은 ORA-02292의 정확한 의미, 발생 시나리오, 차단 자식 행 탐색 방법, 그리고 CASCADE·SET NULL·소프트 삭제까지 실전 해결 패턴을 정리한다.

 

ORA-02292

 

한눈에 보기
에러 의미 / 부모 DELETE·PK UPDATE 시 자식 행이 해당 부모 키를 참조 중
주요 원인 / FK 옵션 미설계, 삭제 순서 오류, 소프트/하드 삭제 혼용, FK 인덱스 누락 락 경합, DROP/TRUNCATE 차단
근본 해법ON DELETE CASCADE / SET NULL, 자식 먼저 삭제, FK 컬럼 인덱스, DROP TABLE ... CASCADE CONSTRAINTS

목차

01ORA-02292 정확한 의미와 에러 메시지
02발생 원인 일곱 가지 (DELETE·PK UPDATE·DDL·옵션 누락·삭제 순서·혼용·다중 자식)
03재현 시나리오 — DELETE·UPDATE·TRUNCATE·DROP
04차단 자식 행 탐색 — 동적 SQL로 모든 참조 자식 카운트
05해결 방법 — CASCADE·SET NULL·수동 순차·소프트 삭제
06FK 인덱스 누락이 부르는 락 경합과 데드락
Q&A자주 묻는 질문 5선
07예방 체크리스트와 결론
01 / ORA-02292의 정확한 의미
오라클 공식 메시지는 "integrity constraint (string.string) violated - child record found"이며, 한국어로는 "무결성 제약조건(스키마.제약조건명)이 위배되었습니다 - 자식 레코드가 발견되었습니다"로 표시된다. 11g부터 23ai까지 메시지 변경이 없다.

오라클 공식 원인 설명은 "외래 키 의존성을 가진 부모 키 값을 삭제하려고 시도함"이다. ORA-02291이 자식 측 행위(자식 INSERT·UPDATE)에서 발생하는 반면, ORA-02292는 부모 측 행위(부모 DELETE·UPDATE)에서 발생한다. 두 에러는 외래키 무결성의 양면이며, 에러 메시지에 나오는 제약 이름은 두 경우 모두 외래키 제약의 이름이다.

1. ORA-02292가 던져지는 경로

ORA-02292는 다음 세 시점에서만 발생한다.

  • 부모 테이블 DELETE ━ 삭제하려는 행을 자식이 참조 중
  • 부모 테이블 UPDATE ━ 부모 PK 값을 변경하려는데 자식이 옛 값 참조 중
  • 부모 테이블에 종속된 DML이 일어나도록 가공된 시점 ━ 트리거 내부 등

DDL 측 형제 에러도 있다. TRUNCATE TABLE 부모는 ORA-02266, DROP TABLE 부모는 ORA-02449를 던진다. 모두 같은 외래키 무결성 그룹의 변종이다.

2. 발생 원인 일곱 가지

원인 1 / FK 옵션 설계 누락

기본 FK는 NO ACTION처럼 동작한다. 부모 삭제·PK 변경 시 자동 처리 옵션(CASCADE, SET NULL)을 지정하지 않으면 모든 부모 DELETE가 ORA-02292로 막힌다. 이 케이스가 가장 흔하다.

원인 2 / 삭제 순서 오류

애플리케이션이 부모를 먼저 삭제하려고 시도한다. 자식 정리 로직이 누락됐거나, 트랜잭션 분리가 잘못된 경우가 많다.

-- 잘못된 순서
DELETE FROM dept WHERE dept_id = 10;
-- ORA-02292: emp(dept_id=10)가 참조 중

-- 올바른 순서
DELETE FROM emp  WHERE dept_id = 10;
DELETE FROM dept WHERE dept_id = 10;

원인 3 / 부모 PK UPDATE

부모의 PK 값을 변경하려고 할 때 발생한다. PK 변경은 그 자체로 권장되지 않는 패턴이지만, 데이터 정리 과정에서 종종 시도된다.

-- 부모 PK를 변경하려고 시도 → 자식이 옛 PK 참조
UPDATE dept SET dept_id = 99 WHERE dept_id = 10;
-- ORA-02292

원인 4 / TRUNCATE 차단

TRUNCATE TABLE 부모는 정확히는 ORA-02266 "unique/primary keys in table referenced by enabled foreign keys"가 발생한다. ORA-02292의 사촌 격이다. 자식 행이 비어 있어도 FK가 enable 상태면 차단된다.

원인 5 / DROP TABLE 차단

DROP TABLE 부모도 마찬가지로 ORA-02449 "unique/primary keys in table referenced by foreign keys"가 발생한다. 해결책은 DROP TABLE dept CASCADE CONSTRAINTS;로 참조 FK를 자동 제거하면서 떨어뜨리는 것이다.

원인 6 / 소프트 삭제와 하드 삭제 혼용

일부 모듈은 is_deleted='Y' 플래그만 설정하고, 다른 모듈은 실제 DELETE를 수행하는 시스템에서 발생한다. 자식 모듈은 소프트 삭제로 운영되지만 부모 모듈에서 하드 삭제를 시도하면 자식이 여전히 물리적으로 남아 ORA-02292가 발생한다.

원인 7 / 다중 자식 테이블

한 부모를 여러 자식 테이블이 참조하는 경우 어느 한 자식만 정리하면 다른 자식에서 막힌다. 운영자가 의식하지 못하는 자식 테이블이 숨어 있을 때 디버깅이 어렵다.

3. 재현 시나리오

전체 흐름을 한 번에 보여주는 예시는 다음과 같다.

-- 부모·자식 테이블 생성
CREATE TABLE dept (
  dept_id NUMBER PRIMARY KEY,
  name    VARCHAR2(50)
);

CREATE TABLE emp (
  emp_id  NUMBER PRIMARY KEY,
  dept_id NUMBER,
  CONSTRAINT fk_emp_dept
    FOREIGN KEY (dept_id) REFERENCES dept(dept_id)
);

INSERT INTO dept VALUES (10, 'SALES');
INSERT INTO emp  VALUES (1001, 10);
COMMIT;

-- DELETE 위반
DELETE FROM dept WHERE dept_id = 10;
-- ORA-02292: SCOTT.FK_EMP_DEPT violated - child record found

-- UPDATE 위반
UPDATE dept SET dept_id = 99 WHERE dept_id = 10;
-- ORA-02292

-- TRUNCATE 차단 (다른 코드)
TRUNCATE TABLE dept;
-- ORA-02266

-- DROP 차단 (다른 코드)
DROP TABLE dept;
-- ORA-02449

4. 차단 자식 행 탐색

다중 자식 환경에서는 어떤 자식이 막고 있는지 즉시 알기 어렵다. 동적 SQL로 모든 참조 자식 테이블의 카운트를 한 번에 출력한다.

-- 특정 부모 테이블을 참조하는 모든 FK 조회
SELECT c.constraint_name,
       c.table_name        AS child_table,
       cc.column_name      AS child_col,
       c.delete_rule, c.status
FROM   user_constraints  c
JOIN   user_cons_columns cc ON c.constraint_name = cc.constraint_name
JOIN   user_constraints  r  ON c.r_constraint_name = r.constraint_name
WHERE  c.constraint_type = 'R'
  AND  r.table_name = 'DEPT';

-- 차단 중인 자식 행을 동적으로 카운트
DECLARE
  v_cnt NUMBER;
BEGIN
  FOR r IN (
    SELECT c.table_name, cc.column_name
    FROM   user_constraints  c
    JOIN   user_cons_columns cc ON c.constraint_name = cc.constraint_name
    JOIN   user_constraints  p  ON c.r_constraint_name = p.constraint_name
    WHERE  c.constraint_type = 'R'
      AND  p.table_name = 'DEPT'
  ) LOOP
    EXECUTE IMMEDIATE
      'SELECT COUNT(*) FROM '||r.table_name||
      ' WHERE '||r.column_name||' = :1'
      INTO v_cnt USING 10;
    DBMS_OUTPUT.PUT_LINE(r.table_name||'.'||r.column_name||
                        ' = '||v_cnt);
  END LOOP;
END;
/
디버깅 흐름
ORA-02292가 발생하면 다음 순서로 확인한다. 첫째, 에러 메시지의 제약 이름을 USER_CONSTRAINTS로 조회해 어떤 자식이 어떤 컬럼으로 막고 있는지 확인한다. 둘째, 그 자식 테이블에서 해당 FK 값 일치 행을 SELECT로 찾는다. 셋째, 다중 자식 환경이면 위 동적 SQL로 모든 자식의 카운트를 일괄 확인한다. 넷째, 비즈니스 정책에 맞춰 CASCADE·SET NULL·수동 삭제·소프트 삭제 중 적절한 옵션을 선택한다. 다섯째, 운영 환경이면 FK 컬럼 인덱스 존재 여부를 점검한다.

5. 해결 방법

패턴 A / ON DELETE CASCADE

부모 삭제 시 자식도 함께 삭제되어야 하는 비즈니스라면 가장 깔끔하다. 자식 데이터가 부모 데이터에 본질적으로 종속된 경우(주문-주문상세, 게시글-댓글 등)에 적합하다.

-- 기존 FK 제거 후 CASCADE 옵션으로 재생성
ALTER TABLE emp DROP CONSTRAINT fk_emp_dept;
ALTER TABLE emp ADD CONSTRAINT fk_emp_dept
  FOREIGN KEY (dept_id) REFERENCES dept(dept_id)
  ON DELETE CASCADE;

-- 이제 부모 삭제 시 자식 자동 삭제
DELETE FROM dept WHERE dept_id = 10;

패턴 B / ON DELETE SET NULL

자식 데이터는 보존하되 부모와의 연결만 끊는 비즈니스라면 SET NULL이 적합하다. 직원-부서 관계에서 부서가 사라져도 직원 정보는 남아야 하는 경우가 대표적이다. FK 컬럼이 NOT NULL이 아니어야 한다.

-- 부모 삭제 시 자식 FK를 NULL로
ALTER TABLE emp ADD CONSTRAINT fk_emp_dept
  FOREIGN KEY (dept_id) REFERENCES dept(dept_id)
  ON DELETE SET NULL;

패턴 C / 수동 순차 삭제

CASCADE를 자동 적용하면 대량 삭제가 발생할 수 있어 위험한 경우에는 명시적으로 자식부터 삭제한다. 트랜잭션으로 묶고 ROLLBACK 가능성을 확보한다.

-- 자식 → 부모 순서 (명시적, 안전)
DELETE FROM emp  WHERE dept_id = 10;
DELETE FROM dept WHERE dept_id = 10;
COMMIT;

패턴 D / 소프트 삭제

물리 삭제 대신 플래그로 논리 삭제하는 방식이다. ORA-02292가 원천적으로 발생하지 않으며, 감사·복구·이력 추적이 용이하다.

-- 부모 컬럼에 삭제 플래그 추가
ALTER TABLE dept ADD (is_deleted CHAR(1) DEFAULT 'N',
                  deleted_at DATE);

-- DELETE 대신 UPDATE로 논리 삭제
UPDATE dept
SET    is_deleted = 'Y', deleted_at = SYSDATE
WHERE  dept_id = 10;

-- 조회 시 플래그로 필터
SELECT * FROM dept WHERE is_deleted = 'N';

패턴 E / DROP TABLE CASCADE CONSTRAINTS

부모 테이블 자체를 떨어뜨려야 할 때는 CASCADE CONSTRAINTS 옵션으로 참조 FK까지 자동 제거한다. 자식 테이블의 데이터는 유지되지만 FK 제약만 사라진다.

-- 참조 FK 자동 제거하며 부모 삭제
DROP TABLE dept CASCADE CONSTRAINTS;

6. FK 인덱스 누락이 부르는 락 경합

ORA-02292 자체와는 직접 관계없지만, 외래키 운영에서 가장 자주 무시되는 함정이 있다. 자식 테이블의 FK 컬럼에 인덱스가 없으면, 부모에서 DELETE 또는 PK UPDATE가 발생할 때 오라클이 자식 테이블 전체에 공유 락을 획득한다. 이 락은 동시 DML을 차단해 운영 환경에서 성능 저하와 ORA-00060(데드락)으로 이어진다.

-- 모든 FK 컬럼에 인덱스 권장
CREATE INDEX ix_emp_dept_id ON emp(dept_id);

-- 인덱스 누락 FK 색출 쿼리
SELECT c.table_name, cc.column_name, c.constraint_name
FROM   user_constraints  c
JOIN   user_cons_columns cc ON c.constraint_name = cc.constraint_name
WHERE  c.constraint_type = 'R'
  AND NOT EXISTS (
    SELECT 1 FROM user_ind_columns ic
    WHERE  ic.table_name  = cc.table_name
      AND  ic.column_name = cc.column_name
      AND  ic.column_position = cc.position
  );

인접 에러와의 구분

에러 코드 검사 대상 전형적 시나리오
ORA-02292 참조 무결성 (부모 → 자식) 부모 DELETE·PK UPDATE 시 자식 존재
ORA-02291 참조 무결성 (자식 → 부모) 자식 INSERT·UPDATE 시 부모 없음
ORA-02266 TRUNCATE 차단 enable FK 존재 부모 TRUNCATE
ORA-02449 DROP 차단 참조 FK 존재 부모 DROP TABLE
ORA-00060 데드락 FK 인덱스 누락 락 경합
Q & A — 자주 묻는 다섯 가지
Q1. ON DELETE CASCADE는 무조건 적용해도 되나요?
적용 가능한 비즈니스 모델인지 신중히 검토해야 한다. 주문-주문상세, 게시글-댓글처럼 부모가 사라지면 자식의 의미도 사라지는 강한 종속 관계에서는 자연스럽다. 반면 직원-부서, 고객-주문처럼 자식이 부모 없이도 의미를 갖는 관계에서는 SET NULL이나 소프트 삭제가 더 적절하다.

Q2. CASCADE로 인해 의도하지 않은 대량 삭제가 일어났습니다.
운영 환경에서 가장 위험한 시나리오다. 단일 DELETE 한 번으로 수십만 행이 사라질 수 있다. CASCADE를 적용한 테이블에서는 부모 삭제 권한을 제한하고, 대량 삭제 전 영향 행 수를 미리 확인하는 SQL을 매뉴얼에 포함해야 한다. 트랜잭션으로 묶어 ROLLBACK 가능 상태를 유지하는 것도 기본 안전장치다.

Q3. 부모 PK를 변경할 수 있는 방법이 있나요?
오라클은 명시적인 ON UPDATE 옵션을 제공하지 않는다. 다른 DB의 ON UPDATE CASCADE에 해당하는 기능이 없다. PK 변경이 필요하면 (1) FK를 일시 DISABLE하고 (2) 부모 PK와 자식 FK를 함께 UPDATE한 뒤 (3) FK를 다시 ENABLE하는 단계를 수동으로 거쳐야 한다. 본질적으로 PK는 변경하지 않도록 모델링하는 것이 옳다.

Q4. 소프트 삭제로 전환하면 무결성은 어떻게 보장되나요?
물리 무결성은 약해지고 논리 무결성(애플리케이션·뷰 레벨)에 의존한다. 모든 조회 쿼리에 is_deleted='N' 필터를 일관되게 적용해야 하며, 누락 시 삭제된 부모와 살아 있는 자식의 부정합이 노출된다. 뷰로 래핑하거나 행 수준 보안(VPD)을 사용하면 누락을 줄일 수 있다.

Q5. FK 컬럼 인덱스는 자동으로 생성되지 않나요?
PK·UK 컬럼은 자동으로 인덱스가 생성되지만 FK 컬럼은 자동 생성되지 않는다. 운영자가 명시적으로 생성해야 한다. FK 컬럼 인덱스 누락은 락 경합·데드락·전체 테이블 잠금의 가장 흔한 원인이므로 테이블 설계 직후 인덱스 생성을 반드시 포함하는 것이 좋다.

7. 예방 체크리스트와 결론

ORA-02292는 FK 옵션 설계와 삭제 정책 통일로 거의 100% 차단할 수 있다. 새 테이블·새 모듈·새 마이그레이션 설계 시 다음 항목을 점검한다.

예방 체크리스트
  • FK 정의 시 ON DELETE 옵션 명시 (CASCADE·SET NULL·NO ACTION 중 선택)
  • 모든 FK 컬럼에 인덱스 생성 (락 경합·데드락 예방)
  • 삭제 순서: 자식 먼저, 부모 나중 표준화 (CASCADE 비사용 시)
  • 부모 PK는 불변(immutable)로 모델링 (UPDATE 금지)
  • 물리/논리 삭제 정책을 시스템 전체에서 통일
  • 차단 자식 행 탐색용 동적 SQL을 운영 매뉴얼에 포함
  • DROP/TRUNCATE 시 CASCADE CONSTRAINTS 옵션 필요 여부 사전 검토
  • CASCADE 적용 테이블은 대량 삭제 영향 행 수 사전 측정 SQL 표준화

ORA-02292는 단순히 DELETE 한 줄의 실패가 아니라, 시스템의 데이터 모델·삭제 정책·동시성 설계가 얼마나 정교한지를 드러내는 거울이다. FK 옵션을 의식적으로 설계하고, FK 컬럼 인덱스를 빠짐없이 두고, 삭제 정책을 시스템 전체에서 일관되게 적용하면 ORA-02292는 운영 중 만나는 일이 거의 사라진다. 한 줄로 요약하면 ━ 부모를 지울 때 자식을 어떻게 할지 미리 결정하라. 그 결정이 CASCADE든 SET NULL이든 수동 삭제든 소프트 삭제든 어느 쪽이라도 좋다, 단지 결정해야 한다.

 

#ORA02292 #childrecordfound #오라클에러 #외래키 #FK #INTEGRITY #CASCADE #SETNULL #DROPCASCADE #FK인덱스 #락경합 #ORA02291 #ORA02266 #ORA02449 #데이터무결성

반응형

댓글