AI 시대 DB 모델링 & SQL 완벽 마스터4-4 외래키 (FK)
테크타니 LEARN — 섹션 04 · DB 모델링

부모 없는 자식은 못 들어온다,
외래키 (FK) 와 무결성

4-3까지는 관계가 어떻게 생겼는지를 봤습니다. 이번엔 그 관계를 DB가 직접 강제하는 장치 — 외래키(FK)입니다. 존재하지 않는 회원의 주문, 주문이 남아있는 회원의 삭제 — 전부 DB 선에서 거부됩니다. shop DB에서 직접 위반을 시도하며 확인해봅니다.

난이도쉬움소요4–5분준비물shop DB (2-3 완료)핵심FK = 관계의 안전망

이번 섹션의 여정

4-1 ~ 4-3

완료 ✓

4-4 외래키 (FK) 와 무결성

지금 여기

4-5 ERD 읽는 법

설계도 독해

4-6 ~ 4-8

정규화 · 실무 ERD · 종합 실습 ⭐

섹션 5 — AI 협업 SQL 작성법

다음 마일스톤

관계를 그리는 법에서, 지키는 법으로

4-1에서 관계로 생각하는 법을, 4-2에서 부모-자식 구조를, 4-3에서 1:1 · 1:N · N:M 세 가지 유형을 배웠습니다. 그런데 질문이 하나 남아요 — 우리가 머리로 아는 이 관계를, DB는 어떻게 지킬까요? 누군가 실수로 ‘존재하지 않는 회원의 주문’을 밀어 넣으면 어떻게 될까요?

그걸 막아주는 장치가 바로 외래키(Foreign Key, FK) 입니다.

이번 강의 한 줄 요약

FK = 부모-자식 관계를 DB가 지켜주는 안전망.

부모 없는 자식이 못 들어오게, 자식 딸린 부모가 함부로 사라지지 않게 — DB가 직접 막아줍니다.

시작 전 체크
shop DB 적재 완료 (2-3)
부모 / 자식 테이블 개념 (4-2)
DBeaver SQL Editor 열어두기

FK 의 역할 — 한 줄의 계약

shop의 orders 테이블을 다시 볼게요. 사실 2-3에서 Alt + X 로 실행한 init.sql 안에, 이 한 줄이 이미 들어 있었습니다.

CREATE TABLE orders (
  order_id   INT  NOT NULL AUTO_INCREMENT COMMENT '주문ID',
  member_id  INT  NOT NULL                COMMENT '회원ID(FK)',
  ordered_at DATE NOT NULL                COMMENT '주문일',
  PRIMARY KEY (order_id),
  CONSTRAINT fk_orders_member
    FOREIGN KEY (member_id) REFERENCES members(member_id)
);

마지막 두 줄이 핵심입니다. “orders 의 member_id 는 반드시 members 의 member_id 에 존재하는 값이어야 한다” — 이 계약을 DB에 등록하는 문장이에요. 계약이 등록된 순간부터 DB는 이런 시도를 전부 거부합니다.

시도FK 의 반응
존재하지 않는 회원ID(9999)로 주문 INSERT❌ 거부 — 부모 없는 자식 차단
주문이 남아있는 회원 DELETE❌ 거부 — 자식 딸린 부모 보호
members 에 없는 ID 로 member_id UPDATE❌ 거부 — INSERT 와 같은 원리

이렇게 데이터가 항상 일관된 상태를 유지하는 것 — 이걸 무결성(integrity) 이라고 부릅니다.

그럼 FK가 없으면 무슨 일이 벌어질까요? 두 DB를 비교해보면 답이 보입니다.

✕ FK 없는 DB

유령 데이터가 조용히 쌓입니다

회원 1번을 삭제해도 그 회원의 주문 50건은 그대로 남습니다. 존재하지 않는 회원ID를 가리키는 유령 데이터 — 이런 게 100건, 1,000건 쌓이면 “회원ID 1의 매출 1,000만원” 인데 회원 정보는 없는, 분석이 어긋나는 DB가 됩니다. 누구야 대체?

✓ FK 있는 DB

사고가 나기 전에 거부합니다

같은 삭제를 시도하면 DB가 에러로 즉시 거부합니다. 유령 데이터가 태어날 기회 자체가 없어요. CREATE TABLE 의 FK 한 줄이 이 모든 사고를 원천 차단합니다.

직접 실험 — DB 가 진짜 막아줄까

백문이 불여일타 — DBeaver SQL Editor에서 직접 FK를 화나게 해봅시다. 두 가지 위반을 시도합니다.

USE shop;
 
-- 실험 ① 존재하지 않는 회원(9999번)으로 주문 만들기
INSERT INTO orders (member_id, ordered_at) VALUES (9999, '2026-05-11');
 
-- 실험 ② 주문이 남아있는 회원(김철수, 1번) 삭제하기
DELETE FROM members
WHERE
  member_id = 1;

실험 ① — 회원 9999번은 members 에 없습니다. 부모 없는 자식을 넣으려는 시도죠.

실험 ① — 존재하지 않는 부모를 가리키는 INSERT
INSERT INTO orders (member_id, ordered_at) VALUES (9999, '2026-05-11');
 
SQL Error [1452]: Cannot add or update a child row:
a foreign key constraint fails (fk_orders_member)
 
회원 9999번이 members 에 없음 → DB 가 INSERT 자체를 거부
안전망 정상 작동 — 유령 주문 차단

실험 ② — 이번엔 반대 방향입니다. 김철수(1번)는 주문을 6건이나 갖고 있는 부모예요.

실험 ② — 자식이 남아있는 부모 DELETE
SELECT
COUNT(*) AS cnt
FROM
orders
WHERE
member_id = 1;
 
cnt
───
6
 
DELETE FROM members
WHERE
member_id = 1;
 
SQL Error [1451]: Cannot delete or update a parent row:
a foreign key constraint fails (fk_orders_member)
 
김철수의 주문 6건이 orders 에 남아있음 → DB 가 삭제 거부
안전망 정상 작동 — 자식 딸린 부모 보호
실험은 마음껏 하세요

두 시도 모두 거부됐으니 DB는 아무것도 바뀌지 않았습니다. 설령 실험하다 뭔가 꼬여도 — init.sql 을 다시 Alt + X 하면 언제든 처음 상태로 돌아옵니다.

ON DELETE 옵션 — 부모가 사라질 때 자식은

실험 ②에서 삭제가 거부된 건 FK의 기본 정책 때문입니다. 사실 FK를 만들 때 “부모가 삭제되면 자식을 어떻게 할지” 를 직접 정할 수 있어요. 선택지는 세 가지입니다.

옵션부모 삭제 시어울리는 관계
RESTRICT (기본값)자식이 있으면 삭제 거부대부분의 경우 — 가장 안전
CASCADE자식도 같이 삭제”같이 사라져야 정상” — 게시글과 댓글
SET NULL자식의 FK 컬럼을 NULL 로”부모 없어도 자식은 남아야” — 부서와 직원
-- 게시글이 지워지면 그 댓글도 같이 삭제
FOREIGN KEY (post_id) REFERENCES posts(post_id) ON DELETE CASCADE
 
-- 부서가 없어져도 직원은 남기고, 부서ID 만 NULL 로
FOREIGN KEY (department_id) REFERENCES departments(department_id) ON DELETE SET NULL
학습 단계의 결론 — 외울 필요 없습니다

지금은 그냥 기본값 RESTRICT 그대로 두세요. 자식이 있으면 부모 삭제를 거부 — 데이터를 잃을 위험이 없는 가장 안전한 정책입니다. 나머지 둘은 “이런 선택지가 있다” 정도만 이해해두면 충분해요.

실무에서는 FK 를 안 쓴다던데요?

가끔 “우리 회사는 FK 안 써요” 라는 곳도 있습니다. 이유는 크게 두 가지예요.

  • 성능 — INSERT/UPDATE 마다 부모가 존재하는지 매번 검증하니, 쓰기가 극단적으로 많은 시스템에서는 부담이 될 수 있습니다.
  • 마이그레이션 편의 — 대량 데이터를 옮길 때 FK가 적재 순서를 강제해서 방해가 되기도 합니다.
하지만 — 흔들리지 마세요

“성능 때문에 FK를 빼야 한다” 는 건 데이터가 수억 건을 넘어가는 매우 특수한 단계의 이야기입니다. 학습 단계와 일반적인 실무에서는 — FK를 항상 쓰는 게 정답. 유령 데이터가 잡아먹는 비용이 FK 검증 비용보다 훨씬 큽니다.

AI 시대의 FK — 정확한 JOIN 경로 지도

2-4에서 만든 AI 과외 선생님이 user_id 가 아니라 member_id 로 정확히 답했던 것, 기억하시나요? init.sql 안의 FK 정의 덕분입니다. AI에게 SQL을 시킬 때 FK 정보만 같이 넘겨줘도, AI가 JOIN 의 ON 조건을 추측 없이 정확히 잡습니다.

[FK 정보]
orders.member_id       → members.member_id
order_items.order_id   → orders.order_id
order_items.product_id → products.product_id
products.category_id   → categories.category_id
FK 정보 = AI 에게 주는 JOIN 경로 지도

이 네 줄이 shop DB 의 모든 JOIN 경로입니다. 섹션 5에서 AI에게 컨텍스트를 설계해주는 법을 배울 때, 이 FK 블록이 핵심 재료가 됩니다.

자주 막히는 지점

① DELETE 가 에러 없이 그냥 성공해버렸어요

17~20번 회원(황태웅·안소영·류재민·백서윤)은 주문이 한 건도 없습니다 — 자식 없는 부모라서 삭제가 허용됩니다.

FIX ▸그게 정상 동작입니다. 자식이 없는 부모는 FK가 막을 이유가 없어요. 지워진 데이터는 init.sql 재실행(Alt + X) 으로 복구하세요.
② 에러 메시지가 너무 길어서 못 읽겠어요

실제 에러에는 DB명·테이블명·제약 정의가 길게 붙지만, 읽어야 할 부분은 두 곳뿐입니다.

FIX ▸핵심은 a foreign key constraint fails 한 구절 + 괄호 안의 제약 이름(fk_orders_member) — 어떤 FK가 막았는지 알려줍니다.
③ 실험하다 shop DB 를 망가뜨릴까 무서워요

위반 실험은 전부 거부되므로 애초에 데이터가 바뀌지 않습니다. 마음껏 깨뜨려보세요.

FIX ▸init.sql 첫머리가 DROP DATABASE IF EXISTS 라서 — Alt + X 한 번이면 언제든 처음 상태로 돌아옵니다.

관계를 그리는 법(4-1~4-3), 그리고 지키는 법(4-4)까지 끝났습니다. 다음은 실무에서 ERD 한 장을 받았을 때 그 설계도를 읽어내는 눈 — 방금 배운 FK가 ERD에서 테이블을 잇는 ‘선’ 이 됩니다.

NEXT LESSON4-4 연습 문제

FK 위반 시나리오를 직접 판별해보며 안전망 감각을 손에 붙입니다.

GO ▸ 연습 문제 풀기