바이브 코딩 시대의 데이터베이스3-5 외래키 (FK)
테크타니 LEARN — 섹션 03 · 테이블에 대한 이해

외래키(FK) —
테이블 사이를 연결하는 값

지난 강의의 기본키는 한 테이블 안에서 행 하나를 정확히 찾는 이름표였습니다. 그런데 회원은 members 테이블에, 주문은 orders 테이블에 따로 들어 있죠. 주문 한 건만 보고 어느 회원의 주문인지 알아내려면 — 자식 테이블에 부모 행의 주소를 적어두면 됩니다. 그 주소가 외래키입니다.

난이도쉬움소요8분준비물3-4 완료오늘이미 정의된 FK 를 읽는 연습

이번 섹션의 여정

3-3 데이터 타입

완료 ✓

3-4 기본키(PK)

완료 ✓

3-5 외래키(FK)

지금 여기

3-6 ~ 3-8

테이블 만들기 · 변경 · 정리

오늘 확인할 네 가지

외래키를 읽을 때 보는 것
값 따라가기 — 주문에서 회원까지
부모와 자식 — 화살표의 방향
참조 무결성 — 없는 곳은 가리킬 수 없다
이름이 아니라 규칙 — 무엇이 연결을 만드는가

주문 한 건은 누구의 주문일까

먼저 members 테이블입니다. 3-4에서 본 그대로, member_id가 기본키예요.

shop DB — members (일부)
member_id (PK)member_namegenderagejoined_at
1김철수M352025-01-15
2이영희F282025-02-03
3박민수M422025-02-10
20백서윤F212026-04-02
회원 20명 중 일부만 보여줍니다

이번에는 orders 테이블입니다.

shop DB — orders (일부)
order_id (PK)member_id (FK)ordered_at
112025-02-01
222025-02-15
332025-03-01
442025-03-15
orders 어디에도 '김철수' 라는 이름은 적혀 있지 않습니다
주문 40건 중 앞부분입니다

주문 1번에는 회원 이름 대신 회원 번호 1만 적혀 있습니다. 이름을 다시 적지 않은 이유는 간단해요 — 번호 1을 따라 members 테이블로 가면 김철수 회원을 정확히 찾을 수 있기 때문입니다.

이렇게 다른 테이블의 행을 가리키는 컬럼을 외래키, 영어로 Foreign Key, 줄여서 FK라고 부릅니다. orders.member_idmembers.member_id를 가리키는 주소 역할을 합니다.

오늘의 핵심 문장외래키는 자식 테이블에 적어두는 부모 행의 정확한 주소입니다.

값 하나를 따라가 보기

말로만 보면 추상적이니 값의 흐름을 천천히 따라가 보겠습니다.

주문 1번 → 회원 이름 찾기
단계무엇을 하는가얻는 것
orders 에서 order_id 가 1인 행을 찾는다member_id = 1
그 숫자 1을 들고 members 로 간다기본키 member_id 에서 1번 행
members 1번 행을 읽는다member_name = 김철수
그래서 주문 1번은 김철수 회원의 주문입니다

같은 방식으로 주문 2번도 읽어보세요. member_id가 2이고, members의 2번은 이영희입니다. 주문 3번은 member_id가 3, members의 3번은 박민수예요.

여기서 중요한 지점이 하나 있습니다. 두 테이블에 숫자 1이 우연히 같아 보이는 게 아니라는 점이에요.

값이 같은 것과 연결된 것은 다릅니다

orders.member_idmembers.member_id를 참조하도록 외래키 규칙이 실제로 설정되어 있습니다. 이 차이가 왜 중요한지는 뒤에서 다시 짚겠습니다.

부모 테이블과 자식 테이블

외래키를 읽을 때는 부모자식이라는 말을 자주 씁니다. 두 단어의 뜻은 딱 하나로 정해져 있어요.

누가 부모이고 누가 자식인가
테이블이 컬럼어떤 키인가역할
membersmember_id기본키 (PK)가리킴을 받는 쪽 — 부모
ordersmember_id외래키 (FK)주소를 적어두는 쪽 — 자식
화살표는 언제나 자식의 FK → 부모의 PK 방향입니다
방향을 헷갈리지 않는 한 문장부모가 주소를 가지고 있고, 자식이 그 주소를 적어둔다.

우리 예에서는 화살표를 orders.member_id에서 members.member_id 쪽으로 그립니다. 자식의 외래키에서 부모의 기본키로 따라가는 화살표예요.

오해하기 쉬운 지점

부모가 더 중요하고 자식이 덜 중요하다는 뜻이 아닙니다. 데이터가 어느 방향을 가리키는지 설명하는 이름일 뿐이에요. 주문 데이터가 회원 데이터보다 덜 중요할 이유는 없죠.

한 부모를 여러 자식이 가리킬 수 있다

orders를 더 내려가 보면 member_id 1이 여러 번 나옵니다.

members 의 1번(김철수)을 가리키는 주문들
order_id (PK)member_id (FK)ordered_at
112025-02-01
612025-04-10
1412025-08-01
2012025-10-12
2712025-12-10
3412026-02-15
주문 6건이 모두 members 의 1번 행 하나를 가리킵니다
회원 한 명이 여러 번 주문할 수 있으니까요

부모인 회원 한 행을, 자식인 주문 여러 행이 가리킬 수 있습니다. 그렇다면 반대는 어떨까요? 부모 행이 있다고 자식 행이 꼭 있어야 하는 것은 아닙니다.

정상입니다 — 오류가 아닙니다

우리 샘플 데이터의 20번 백서윤 회원은 members에는 있지만 orders에는 주문이 하나도 없습니다. 가입은 했는데 아직 구매하지 않은 상태예요. 회원으로 가입했다고 반드시 주문해야 하는 건 아니니까요 — 데이터가 깨진 게 아니라 그냥 아직 주문이 없는 것입니다.

이런 관계를 어떤 종류와 개수로 설계할지는 섹션 4에서 제대로 다룹니다. 지금은 외래키 값 하나를 따라가면 연결된 부모 행을 찾을 수 있다는 흐름에만 집중하면 충분해요.

참조 무결성 — 존재하는 부모만 가리킨다

이제 외래키가 단순한 메모와 다른 가장 중요한 이유를 볼 차례입니다.

새 주문을 저장하면서 member_id999를 넣었다고 해보겠습니다. 그런데 members 테이블에는 999번 회원이 없어요. 이 주문을 그대로 허용하면 누구의 주문인지 영원히 찾을 수 없는 기록이 생깁니다.

외래키가 설정되어 있으면 데이터베이스가 저장하기 전에 부모 테이블을 확인합니다. 없는 번호라면 오류를 내고 저장을 막아요.

MySQL — 없는 회원 번호로 주문을 저장해 보면
INSERT INTO orders (member_id, ordered_at) VALUES (999, '2026-05-01');
ERROR 1452 (23000): Cannot add or update a child row:
a foreign key constraint fails (`shop`.`orders`,
CONSTRAINT `fk_orders_member` FOREIGN KEY (`member_id`)
REFERENCES `members` (`member_id`))
 
→ 잘못 저장된 뒤 깨진 게 아니라, 저장 자체가 거부되었습니다

이렇게 테이블 사이의 연결이 깨지지 않도록 지키는 규칙을 참조 무결성이라고 합니다.

참조 무결성을 쉽게 말하면주소를 적었다면, 실제로 존재하는 곳이어야 한다.
INSERT 문법은 아직 몰라도 됩니다

위 화면에서 눈여겨볼 건 첫 줄의 문법이 아니라 결과 메시지입니다. 데이터를 넣는 SQL은 뒤에서 따로 배웁니다 — 지금은 “없는 부모를 가리키면 DB가 막는다” 는 사실만 가져가세요.

우리 orders.member_id에는 NOT NULL도 함께 걸려 있습니다. 그래서 회원 번호를 비워둘 수도 없고, 존재하지 않는 번호를 적을 수도 없습니다. 모든 주문은 반드시 실제 회원 한 명을 가리켜야 해요.

외래키라고 해서 무조건 NOT NULL 은 아닙니다

방금 문장을 이렇게 오해하기 쉽습니다 — “외래키 컬럼은 원래 비울 수 없구나.” 그렇지 않아요.

FK 컬럼의 빈값 규칙은 따로 정합니다
FK 컬럼의 설계비울 수 있나비워두면 무슨 뜻
NULL 허용가능아직 연결된 부모가 없다
NOT NULL불가반드시 부모 하나를 가리켜야 한다
외래키 자체가 NULL 을 금지하는 게 아닙니다 — NOT NULL 은 별개의 약속입니다

외래키 컬럼이 NULL을 허용하도록 설계된 테이블이라면, 아직 연결된 부모가 없다는 뜻으로 NULL을 넣을 수 있습니다. 우리 주문 테이블이 비울 수 없는 건 orders.member_idNOT NULL로 정했기 때문이에요.

우리 shop DB 의 실제 값

orders.member_idproducts.category_id둘 다 NOT NULL 입니다. NULL을 허용하는 외래키는 이 강의에서 개념만 짚는 가상의 설계예요.

부모 행을 함부로 지울 수 없는 이유

참조 무결성은 데이터를 넣을 때만이 아니라 지울 때도 작동합니다.

김철수 회원 1번을 여러 주문이 가리키고 있는데 회원 1번을 먼저 지워버리면, 남은 주문 6건이 갈 곳을 잃습니다. 우리 실습 DB에는 삭제 동작을 따로 바꾸는 옵션이 없어서 — MySQL이 외래키 오류를 내고 삭제를 막습니다.

MySQL — 주문이 남아 있는 회원을 지우려 하면
DELETE FROM members WHERE member_id = 1;
ERROR 1451 (23000): Cannot delete or update a parent row:
a foreign key constraint fails (`shop`.`orders`,
CONSTRAINT `fk_orders_member` FOREIGN KEY (`member_id`)
REFERENCES `members` (`member_id`))
 
→ 연결된 주문을 어떻게 할지 먼저 정해야 합니다

같은 규칙을 여러 삭제 시도에 적용해 보면 이렇게 갈립니다.

우리 shop DB 에서 삭제 시도의 결과
삭제 시도결과이유
회원 1번(김철수)차단주문 6건이 이 회원을 가리키고 있음
회원 20번(백서윤)성공이 회원을 가리키는 주문이 하나도 없음
주문 1번차단order_items 두 줄이 주문 1번을 가리키고 있음
가리키는 자식이 남아 있으면 부모를 지울 수 없습니다
orders 는 members 의 자식이면서 동시에 order_items 의 부모입니다

반대 방향도 확인해 두세요. 자식인 주문 행이 사라진다고 부모인 회원까지 자동으로 사라지지는 않습니다. 주문 34번이 지워지더라도 회원 1번 김철수는 members에 그대로 남아 있어요 — 검사는 언제나 부모를 지울 때 자식이 남아 있는지 보는 방향으로만 작동합니다.

삭제 정책은 설계로 정합니다

삭제할 때 자식까지 함께 지울지, 막을지, 연결만 비울지는 설계에 따라 다른 정책을 정할 수 있습니다. 그 선택지의 문법은 이번 강의에서 다루지 않아요. 오늘은 우리 DB에서는 연결된 자식이 있는 부모를 함부로 지울 수 없다는 것만 정확히 기억하면 됩니다.

컬럼 이름이 같다고 외래키가 되는 건 아닙니다

여기서 아주 흔한 오해 하나를 짚겠습니다. 두 테이블에 member_id라는 같은 이름의 컬럼이 있다고 해서 자동으로 연결되지 않습니다.

이름만 같은 두 컬럼
  • DB 가 아는 것두 컬럼이 같은 이름이라는 사실뿐

  • 999 를 넣으면그대로 저장된다 — 막아주지 않는다

  • 부모 삭제아무 검사 없이 지워진다

FOREIGN KEY 규칙이 있는 두 컬럼 ✓
  • DB 가 아는 것이 값이 어느 테이블의 어느 컬럼을 가리키는지

  • 999 를 넣으면부모를 확인하고 저장을 거부한다

  • 부모 삭제가리키는 자식이 있으면 차단한다

같은 값이 나란히 놓여 있는 것과 — 연결이 보장되는 것은 다릅니다.

연결과 검사를 실제로 만들어내는 건 설계도에 적힌 이 한 줄입니다. 우리 orders 테이블에 실제로 들어 있는 규칙이에요.

CONSTRAINT fk_orders_member
  FOREIGN KEY (member_id) REFERENCES members(member_id)

쉽게 읽으면 *“이 테이블의 member_idmembers 테이블의 member_id를 참조한다”*는 뜻입니다. 그리고 반대로 — 컬럼 이름이 서로 달라도 외래키 규칙으로 연결할 수 있습니다. 이름을 맞추는 건 사람이 읽기 쉽게 하려는 관례이고, 실제 연결은 FOREIGN KEY 제약조건이 만듭니다.

카테고리와 상품에서도 같은 구조

이 구조는 회원과 주문에만 있는 게 아닙니다. categoriesproducts에서 똑같이 반복돼요.

shop DB — categories (부모)
category_id (PK)category_name
1의류
2전자기기
3식품
카테고리 5개 중 일부
shop DB — products (자식)
product_id (PK)product_namepricecategory_id (FK)
1라운드 티셔츠190001
8패딩 점퍼1290001
9무선 이어폰890002
11보조배터리250002
16프리미엄 그래놀라120003
라운드 티셔츠의 category_id 는 1 — 따라가면 '의류' 가 나옵니다
상품 30개 중 일부

무선 이어폰의 category_id는 2이고, 2번 카테고리는 전자기기입니다. 여기서는 categories가 부모, products가 자식이에요. 화살표는 products.category_idcategories.category_id.

두 관계를 나란히 두면 규칙이 똑같습니다
자식 (FK)부모 (PK)읽는 법
orders.member_idmembers.member_id이 주문은 어느 회원의 것인가
products.category_idcategories.category_id이 상품은 어느 카테고리인가
여러 자식이 같은 부모를 가리킬 수 있고 — 없는 부모는 가리킬 수 없습니다

여러 상품이 같은 카테고리 번호를 가질 수 있지만, 존재하지 않는 카테고리 번호를 상품에 넣을 수는 없습니다. 회원과 주문에서 본 규칙이 그대로예요.

표를 합치는 SQL 은 섹션 5 에서

지금은 값을 따라가며 연결된 행을 찾는 개념만 잡습니다. 두 테이블을 실제로 하나의 결과로 합치는 JOIN 문법은 섹션 5에서 제대로 다룹니다.

한눈에 정리

개념한 줄 정의shop 에서는
외래키 (FK)자식 테이블에 적어둔 부모 행의 주소orders.member_id
부모 테이블가리킴을 받는 쪽 — 기본키를 가진 테이블members · categories
자식 테이블외래키로 가리키는 쪽orders · products · order_items
화살표 방향언제나 자식의 FK → 부모의 PKorders.member_idmembers.member_id
참조 무결성존재하지 않는 부모는 가리킬 수 없다는 규칙member_id = 999 저장 거부
부모 삭제가리키는 자식이 남아 있으면 삭제 차단주문이 있는 회원 1번은 삭제 불가
FK ≠ NOT NULL빈값 허용 여부는 별개의 약속우리 FK 컬럼은 모두 NOT NULL
한 걸음 더 — 정확히 말하면

외래키가 UNIQUE처럼 중복을 허용하지 않는 다른 키를 참조할 수도 있습니다. 다만 우리 샘플 DB의 외래키는 전부 부모 테이블의 기본키를 가리켜요. 입문 단계에서는 이 구조부터 확실히 익히면 충분합니다.

여기까지 왔다면 컬럼, 데이터 타입, 기본키, 외래키 — 테이블을 만드는 재료를 전부 살펴본 셈입니다. 다음은 이 재료를 SQL로 직접 적어서 새 테이블을 만들 차례예요.

NEXT LESSON3-5 연습 문제

값을 따라가 회원을 찾고, 부모·자식을 판별하고 — 저장과 삭제가 언제 차단되는지 직접 판정합니다.

GO ▸ 연습 문제 풀기