게임 데이터베이스를 만든다고 생각해보자.
게임에는 수많은 데이터가 존재한다.
플레이어
캐릭터
아이템
길드
퀘스트
인벤토리
예를 들어 플레이어 정보를 다음과 같이 저장할 수 있다.
player_id | nickname | level | |
|---|---|---|---|
1001 | 천검 | player1@game.com | 35 |
1002 | 마법사킴 | player2@game.com | 21 |
1003 | 탱커박 | player3@game.com | 42 |
그런데 데이터베이스에 아무런 규칙도 없다면 문제가 발생할 수 있다.
player_id = 1001인 플레이어가 두 명이면?
같은 이메일로 계정이 여러 개 만들어지면?
존재하지 않는 플레이어에게 아이템을 지급하면?
이러한 잘못된 데이터가 저장되는 것을 막기 위해 데이터베이스에 **제약조건(Constraint)**을 설정한다.
그중 관계형 데이터베이스에서 가장 기본적으로 사용되는 것이 PK, FK, UK다.
제약조건(Constraint)이란?
제약조건은 말 그대로 데이터베이스에 저장되는 데이터가 반드시 지켜야 하는 규칙이다.
게임 운영자가 다음과 같은 규칙을 만들었다고 생각하면 쉽다.
"모든 플레이어는 서로 다른 고유번호를 가져야 한다."
"동일한 이메일로 두 개의 계정을 만들 수 없다."
"존재하지 않는 플레이어에게 아이템을 지급할 수 없다."
이러한 규칙을 애플리케이션 코드에서 검사할 수도 있지만, 데이터베이스 자체에도 규칙을 설정할 수 있다.
PRIMARY KEY
FOREIGN KEY
UNIQUE
NOT NULL
CHECK
이번에는 그중 데이터를 식별하고 연결하는 데 중요한 PK, FK, UK를 게임 데이터베이스를 통해 알아보자.
PK(Primary Key) — 플레이어의 고유번호
**Primary Key(PK, 기본키)**는 테이블에서 각 행(Row)을 고유하게 식별하기 위한 값이다.
게임의 플레이어 테이블을 생각해보자.
player_id | nickname | level |
|---|---|---|
1001 | 천검 | 35 |
1002 | 마법사킴 | 21 |
1003 | 탱커박 | 42 |
여기서 player_id를 PK로 사용할 수 있다.
CREATE TABLE player (
player_id INTEGER PRIMARY KEY,
nickname VARCHAR(30),
level INTEGER
);
player_id = 1001
게임 서버에서 이라고 하면 데이터베이스는 정확하게 한 명의 플레이어를 찾을 수 있다.
왜 닉네임을 PK로 사용하지 않을까?
닉네임도 중복을 금지하면 고유한 값으로 사용할 수 있다.
하지만 닉네임은 변경될 가능성이 있다.
천검
↓ 닉네임 변경
천상의검
반면 내부적으로 사용하는 player_id는 일반적으로 변경할 필요가 없다.
player_id = 1001
닉네임: 천검
↓
닉네임: 천상의검
player_id = 1001 ← 그대로
그래서 실제 데이터베이스에서는 의미가 있는 이름보다 변하지 않는 ID 값을 PK로 사용하는 경우가 많다.
PK의 중요한 특징
PK에는 중요한 규칙이 있다.
① 중복될 수 없다
1001 | 천검
1001 | 마법사킴 ← 불가능
두 플레이어가 동일한 player_id를 가질 수 없다.
② NULL이 될 수 없다
NULL | 천검 ← 불가능
PK는 데이터를 식별해야 하기 때문에 값이 반드시 존재해야 한다.
즉 PK의 핵심은 다음과 같다.
PK = 이 데이터가 정확히 누구인지 식별하기 위한 고유한 값

UK(Unique Key) — 이메일과 닉네임 중복 금지
이번에는 플레이어의 이메일을 생각해보자.
player_id | nickname | |
|---|---|---|
1001 | 천검 | player1@game.com |
1002 | 마법사킴 | player2@game.com |
1003 | 탱커박 | player3@game.com |
우리 게임에서는 하나의 이메일로 하나의 계정만 만들 수 있다고 가정하자.
그런데 PK는 이미 player_id다.
그렇다고 이메일의 중복을 허용하면 다음과 같은 데이터가 만들어질 수 있다.
1001 | 천검 | player1@game.com
1002 | 마법사킴 | player1@game.com
이때 사용할 수 있는 것이 UNIQUE 제약조건이다.
CREATE TABLE player (
player_id INTEGER PRIMARY KEY,
nickname VARCHAR(30),
email VARCHAR(100) UNIQUE
);
이제 동일한 이메일을 다시 저장하려고 하면 데이터베이스가 거부한다.
player1@game.com ← 이미 존재
player1@game.com ← 저장 불가
즉 UK(Unique Key)는 특정 컬럼의 값이 중복되지 않도록 만드는 역할을 한다.
PK와 UK는 뭐가 다른가?
둘 다 중복을 허용하지 않기 때문에 처음에는 비슷해 보인다.
게임 계정으로 비교하면 이해하기 쉽다.
Player
player_id = 1001
nickname = 천검
email = player1@game.com
여기서
player_id → PK
email → UNIQUE
로 설정할 수 있다.player_id는 플레이어라는 데이터를 대표해서 식별하는 값이다.
반면 email은 플레이어 자체를 대표하기 위해 만든 키라기보다 업무 규칙상 중복되면 안 되는 값이다.
구분 | PK | UNIQUE |
|---|---|---|
주요 목적 | 행을 대표하여 식별 | 값의 중복 방지 |
중복 | 불가능 | 불가능 |
NULL | 불가능 | DBMS에 따라 처리 차이 존재 |
한 테이블에 여러 개 | 불가능 | 가능 |
예시 | player_id | email, nickname |
예를 들어 다음처럼 여러 UNIQUE 제약조건을 둘 수도 있다.
CREATE TABLE player (
player_id INTEGER PRIMARY KEY,
nickname VARCHAR(30) UNIQUE,
email VARCHAR(100) UNIQUE
);
그러면
player_id → 플레이어 식별
nickname → 중복 금지
email → 중복 금지
라는 서로 다른 역할을 수행한다.
참고로
UNIQUE의NULL처리 방식은 DBMS마다 차이가 있다. PostgreSQL의 일반적인UNIQUE제약조건에서는 여러NULL을 허용할 수 있으므로, 값 자체가 반드시 존재해야 한다면NOT NULL도 함께 고려해야 한다.
FK(Foreign Key) — 다른 테이블과 연결하기
이번에는 플레이어의 인벤토리를 만들어보자.
게임에서는 플레이어 한 명이 여러 아이템을 가지고 있을 수 있다.
Player
player_id | nickname |
|---|---|
1001 | 천검 |
1002 | 마법사킴 |
Inventory
inventory_id | player_id | item_name |
|---|---|---|
1 | 1001 | 전설의 검 |
2 | 1001 | 체력 포션 |
3 | 1002 | 마법 지팡이 |
Inventory의 player_id를 보면 Player 테이블에도 같은 값이 존재한다.
Player
1001 | 천검
↑
│
│ 참조
│
Inventory
1 | 1001 | 전설의 검
2 | 1001 | 체력 포션
이때 Inventory의 player_id가 **FK(Foreign Key, 외래키)**가 된다.
SQL로는 다음처럼 설정할 수 있다.
CREATE TABLE inventory (
inventory_id INTEGER PRIMARY KEY,
player_id INTEGER NOT NULL,
item_name VARCHAR(100),
FOREIGN KEY (player_id)
REFERENCES player(player_id)
);
핵심은 이 부분이다.
FOREIGN KEY (player_id)
REFERENCES player(player_id)
쉽게 읽으면 다음과 같다.
"Inventory의 player_id는 Player 테이블의 player_id를 참조한다."
FK가 없다면 어떤 문제가 생길까?
현재 존재하는 플레이어가 다음 두 명뿐이라고 해보자.
1001 | 천검
1002 | 마법사킴
그런데 실수로 다음 데이터를 저장한다.
Inventory
10 | 9999 | 전설의 검
문제가 있다.
player_id = 9999
→ 그런 플레이어가 없음
그러면 주인 없는 아이템 데이터가 만들어진다.
FK를 설정했다면 데이터베이스는 이를 막을 수 있다.
Inventory에 player_id = 9999 저장 요청
↓
Player에 9999가 있는지 확인
↓
없음 ❌
↓
저장 거부
이처럼 서로 연결된 테이블의 데이터가 모순되지 않도록 유지하는 것을 **참조 무결성(Referential Integrity)**이라고 한다.

PK와 FK는 같은 컬럼에 나타날 수도 있다
PK와 FK를 공부할 때 많이 헷갈리는 부분이 있다.
Player.player_id → PK
Inventory.player_id → FK
이 둘은 이름과 값이 같을 수 있지만 역할이 다르다.
┌─────────────────┐
│ Player │
├─────────────────┤
│ PK player_id │
│ nickname │
└────────┬────────┘
│
│ 1
│
│ N
▼
┌─────────────────┐
│ Inventory │
├─────────────────┤
│ PK inventory_id │
│ FK player_id │
│ item_name │
└─────────────────┘
Player에서는 player_id가
"이 플레이어가 누구인가?"
를 나타내므로 PK다.
Inventory에서는 player_id가
"이 아이템의 주인이 누구인가?"
를 나타내며 Player를 참조하므로 FK다.
게임 데이터베이스를 조금 더 확장해보자
실제 게임처럼 구조를 조금 확장해보자.
Player
PK player_id
UK email
UK nickname
level
Item
PK item_id
item_name
price
Inventory
PK inventory_id
FK player_id
FK item_id
quantity
관계를 그림으로 표현하면 다음과 같다.
┌───────────────┐
│ Player │
├───────────────┤
│ PK player_id │
│ UK email │
│ UK nickname │
│ level │
└───────┬───────┘
│
│ 1
│
│ N
┌───────▼───────┐
│ Inventory │
├───────────────┤
│ PK inventory_id│
│ FK player_id │
│ FK item_id │
│ quantity │
└───────┬───────┘
│ N
│
│ 1
┌───────▼───────┐
│ Item │
├───────────────┤
│ PK item_id │
│ item_name │
│ price │
└───────────────┘
여기에는 세 가지 키가 모두 사용되고 있다.
PK
player_id
item_id
inventory_id
각 테이블의 데이터를 고유하게 식별한다.
UK
email
nickname
게임 정책상 중복되면 안 되는 값을 제한한다.
FK
Inventory.player_id
Inventory.item_id
Inventory를 Player와 Item에 연결한다.

PK, FK, UK를 게임으로 기억하기
세 가지를 어렵게 외울 필요는 없다.
게임 캐릭터 하나를 생각하면 된다.
PK = 캐릭터 고유번호
"너 누구야?"
→ Player ID 1001
데이터를 식별한다.
UK = 중복 불가능한 닉네임
"이미 사용 중인 닉네임입니다."
특정 값의 중복을 막는다.
FK = 아이템의 주인번호
"이 검 누구 거야?"
→ Player ID 1001의 아이템
다른 테이블의 데이터를 참조하고 연결한다.
마무리
PK, FK, UK는 모두 데이터에 규칙을 부여하기 위한 중요한 제약조건이다.
하지만 각각의 목적은 명확하게 다르다.
Key | 역할 | 게임 예시 |
|---|---|---|
PK | 데이터를 고유하게 식별 | 플레이어 고유 ID |
UK | 특정 값의 중복 방지 | 이메일, 닉네임 |
FK | 다른 테이블을 참조하고 연결 | 아이템 소유자의 player_id |
한 문장씩 기억하면 간단하다.
PK는 "누구인지" 구별한다.
UK는 "같은 값"이 들어오는 것을 막는다.
FK는 "누구와 연결되어 있는지" 나타낸다.
이 세 가지를 이용하면 단순히 데이터를 저장하는 것을 넘어 중복된 계정이나 존재하지 않는 플레이어의 아이템처럼 잘못된 데이터가 만들어지는 것을 데이터베이스 단계에서 방지할 수 있다.
그리고 이러한 PK와 FK를 기반으로 테이블 간의 1:1, 1:N, N:M 관계가 만들어지면서 본격적인 관계형 데이터 모델링으로 이어진다.
댓글 0
댓글을 불러오는 중…