테이블 하나에 다 넣으면 안 되나? 처음 데이터베이스를 접하면 이 질문이 자연스럽게 든다. 어차피 같은 DB 안에 있는 데이터인데, 굳이 여러 테이블로 쪼갤 이유가 있냐는 것이다.
있다. 직접 보면 바로 납득이 된다.
쇼핑몰을 만든다고 가정하자. 주문이 들어오면 이런 정보가 필요하다.
처음엔 이걸 테이블 하나로 만들어버리고 싶다.
CREATE TABLE orders (
order_id INT,
order_date DATE,
quantity INT,
member_name VARCHAR(50),
email VARCHAR(100),
product_name VARCHAR(100),
price INT
);
데이터를 넣으면 이런 모양이 된다.
| order_id | order_date | quantity | member_name | product_name | price | |
|---|---|---|---|---|---|---|
| 1 | 2024-03-01 | 2 | 김민수 | kim@email.com | 무선 마우스 | 25000 |
| 2 | 2024-03-02 | 1 | 이지현 | lee@email.com | 기계식 키보드 | 89000 |
| 3 | 2024-03-05 | 1 | 김민수 | kim@email.com | 기계식 키보드 | 89000 |
| 4 | 2024-03-07 | 3 | 김민수 | kim@email.com | 무선 마우스 | 25000 |
겉보기엔 문제없어 보인다. 그런데 조금 더 들여다보면 이상한 점이 보이기 시작한다.
김민수가 주문을 할 때마다 kim@email.com이 그대로 반복해서 들어간다. 무선 마우스를 살 때마다 25000이라는 가격도 계속 중복된다.
지금은 4행짜리 예시니까 별 느낌이 없지만, 주문이 수천 건이 쌓이면 같은 데이터가 수천 번 복사된다. 저장 공간 낭비이기도 하고, 더 큰 문제는 따로 있다.
김민수가 이메일을 바꿨다. kim@email.com → minsu@email.com으로.
그러면 이 테이블에서 김민수가 포함된 행을 전부 찾아서 하나씩 바꿔야 한다. 만약 하나라도 빠뜨리면 어떤 행엔 kim@email.com, 어떤 행엔 minsu@email.com이 섞여 들어간다. 같은 사람인데 이메일이 두 가지가 되는 것이다.
-- 이걸 빠짐없이 다 찾아서 바꿔야 한다
UPDATE orders SET email = 'minsu@email.com' WHERE member_name = '김민수';
실수 한 번으로 데이터가 오염된다.
이지현이 탈퇴했다. order_id = 2 행을 지웠더니, 기계식 키보드 상품 정보(89000)도 같이 사라진다. 상품 자체는 여전히 판매 중인데, 그 정보가 없어진 것이다.
물론 다른 회원이 기계식 키보드를 주문한 행이 남아있으면 괜찮겠지만, 만약 이지현만 그 상품을 주문했다면? 상품 가격 정보가 테이블에서 통째로 사라진다.
문제의 원인은 서로 다른 종류의 정보가 한 테이블에 뒤섞여 있기 때문이다. 회원 정보, 상품 정보, 주문 정보 — 이 세 가지는 성격이 다르다. 각각 따로 관리하면 된다.

CREATE TABLE members (
member_id INT PRIMARY KEY,
member_name VARCHAR(50),
email VARCHAR(100)
);
| member_id | member_name | |
|---|---|---|
| 1 | 김민수 | kim@email.com |
| 2 | 이지현 | lee@email.com |
CREATE TABLE products (
product_id INT PRIMARY KEY,
product_name VARCHAR(100),
price INT
);
| product_id | product_name | price |
|---|---|---|
| 1 | 무선 마우스 | 25000 |
| 2 | 기계식 키보드 | 89000 |
CREATE TABLE orders (
order_id INT PRIMARY KEY,
order_date DATE,
quantity INT,
member_id INT,
product_id INT
);
| order_id | order_date | quantity | member_id | product_id |
|---|---|---|---|---|
| 1 | 2024-03-01 | 2 | 1 | 1 |
| 2 | 2024-03-02 | 1 | 2 | 2 |
| 3 | 2024-03-05 | 1 | 1 | 2 |
| 4 | 2024-03-07 | 3 | 1 | 1 |
주문 테이블에는 이름이나 상품명 대신 member_id, product_id만 들어간다. 회원 정보가 필요하면 members 테이블을, 상품 정보가 필요하면 products 테이블을 참조하는 방식이다.
이제 아까의 문제들이 어떻게 해결되는지 보자.
이메일 변경 — members 테이블에서 딱 한 행만 수정하면 끝이다. 주문이 몇 건이든 상관없다.
UPDATE members SET email = 'minsu@email.com' WHERE member_id = 1;
회원 탈퇴 — members에서 이지현을 지워도 products 테이블의 기계식 키보드 정보는 그대로 남는다. 회원 정보와 상품 정보가 완전히 분리돼 있으니까.
중복 제거 — 무선 마우스의 가격은 products 테이블에 딱 한 번만 저장된다. 아무리 주문이 쌓여도 product_id = 1이라는 숫자만 반복될 뿐이다.
주문 테이블의 member_id는 members 테이블의 member_id를 가리킨다. 이런 관계를 명시적으로 설정해두는 것이 외래 키(Foreign Key)다.
CREATE TABLE orders (
order_id INT PRIMARY KEY,
order_date DATE,
quantity INT,
member_id INT,
product_id INT,
FOREIGN KEY (member_id) REFERENCES members(member_id),
FOREIGN KEY (product_id) REFERENCES products(product_id)
);
FOREIGN KEY ... REFERENCES는 "이 컬럼의 값은 저 테이블의 저 컬럼에 존재하는 값이어야 한다"는 제약이다. members에 없는 member_id로 주문을 넣으려 하면 DB가 거절한다. 없는 회원의 주문이 생기는 상황 자체를 막아버리는 것이다.

| 문제 | 원인 | 해결 |
|---|---|---|
| 같은 정보가 반복 저장됨 | 한 테이블에 모든 정보 혼재 | 테이블 분리 후 ID로 참조 |
| 수정 시 여러 행을 바꿔야 함 | 데이터가 여러 행에 중복 | 원본 테이블에서 한 번만 수정 |
| 삭제 시 다른 정보도 사라짐 | 서로 다른 정보가 같은 행에 묶임 | 테이블이 독립적으로 존재 |
테이블 설계의 기본은 결국 하나다. 같은 성격의 데이터끼리 모으고, 다른 성격의 데이터는 분리한다. 그리고 필요할 때 ID로 연결해서 가져온다.