← 아티클 목록

외래키 고아 레코드 — 참조 무결성 점검과 정리

2028-01-31#database#MySQL#데이터무결성

부모 행이 삭제됐는데 자식 행이 남아 있는 것 — 이게 고아 레코드(orphaned row)입니다. 애플리케이션 레벨에서만 관계를 관리하고 외래키(FK) 제약을 안 걸었거나, FK를 NO ACTION으로 두고 부모를 강제 삭제했을 때 쌓입니다. 집계가 안 맞거나, 상세 조회에서 부모가 NULL로 나오면 의심해야 합니다.

왜 생기는가 — 두 가지 경로

원인상황결과
FK 미설정ORM·앱 코드만 믿고 제약 생략부모 삭제 시 자식 잔존
잘못된 삭제배치/수동 DELETE가 부모만 지움자식이 끊긴 참조 보유

핵심은 DB가 강제하지 않은 관계는 언젠가 반드시 깨진다는 점입니다. 코드는 버그가 나고 배치는 순서가 꼬입니다.

고아 레코드 찾기

자식 테이블에서 부모에 매칭되지 않는 행을 LEFT JOINNULL 필터로 찾습니다.

SQL
-- order_items 중 부모 orders가 사라진 행
SELECT oi.id, oi.order_id
FROM order_items oi
LEFT JOIN orders o ON o.id = oi.order_id
WHERE o.id IS NULL;

-- 개수만 빠르게 파악
SELECT COUNT(*) FROM order_items oi
LEFT JOIN orders o ON o.id = oi.order_id
WHERE o.id IS NULL;

order_idNULL 허용이라면 AND oi.order_id IS NOT NULL을 더해 의도된 빈 참조와 진짜 고아를 구분합니다.

안전하게 정리하는 순서

  1. 백업 먼저 — 삭제 전 대상 행을 별도 테이블로 보존한다. 잘못 지웠을 때 복구 근거가 된다.
SQL
CREATE TABLE order_items_orphan_bak AS
SELECT oi.* FROM order_items oi
LEFT JOIN orders o ON o.id = oi.order_id
WHERE o.id IS NULL;
  1. 소량씩 삭제 — 대량 DELETE는 잠금·복제 지연을 유발하니 배치로 나눈다.
SQL
DELETE oi FROM order_items oi
LEFT JOIN orders o ON o.id = oi.order_id
WHERE o.id IS NULL
LIMIT 1000;   -- 반복 실행
  1. FK 제약 추가 — 정리 후 같은 일이 재발하지 않게 외래키를 건다. 고아가 남아 있으면 추가가 실패하므로, 이 단계가 정리 완료의 검증 역할도 한다.
SQL
ALTER TABLE order_items
  ADD CONSTRAINT fk_oi_order
  FOREIGN KEY (order_id) REFERENCES orders(id)
  ON DELETE CASCADE;

ON DELETE CASCADE는 부모 삭제 시 자식을 함께 지웁니다. 자식을 남기되 참조만 끊으려면 ON DELETE SET NULL을 씁니다.

요점 정리

  • 고아 레코드 = 부모 없는 자식 행. 원인은 FK 미설정 또는 부모만 삭제.
  • LEFT JOIN ... WHERE 부모.id IS NULL로 탐지한다.
  • 정리는 백업 배치 삭제 FK 추가 순서로, FK 추가 성공이 곧 검증이다.
  • 재발 방지는 코드가 아니라 DB의 FK 제약으로 강제한다.

고아 레코드를 직접 만들어 탐지·정리하고 FK 제약으로 막는 실습은 데이터베이스 트랙에서 회원가입 없이 무료로 할 수 있습니다.