기본키(PK),
한 행을 정확히 찾는 기준
지난 강의에서 값에 맞는 그릇을 골랐습니다. 이번에는 그 값들이 모여 만든 한 행을 정확히 가리키는 방법입니다. 이름이 김철수인 회원이 두 명이라면 이름만으로는 둘을 구별할 수 없으니까요.
이번 섹션의 여정
완료 ✓
완료 ✓
지금 여기
외래키 · 테이블 만들기
이름이 같은 회원이 둘이라면
회원 한 명의 나이를 고치려고 합니다. 그런데 이름이 김철수인 회원이 두 명이라면 어떻게 될까요?
| member_name | gender | age | joined_at |
|---|---|---|---|
| 김철수 | M | 35 | 2025-01-15 |
| 김철수 | M | 41 | 2025-06-30 |
값을 찾는 것도, 고치는 것도, 지우는 것도 전부 어느 행인지 먼저 정해야 시작됩니다. 이름으로는 그게 안 되죠.
그래서 테이블에는 각 행을 하나씩 구별하는 값이 필요합니다. 이 값을 기본키, 또는 PK라고 부릅니다.
실제 shop 의 회원 20명에는 동명이인이 없습니다. 위 표는 “이름으로는
구별할 수 없다” 는 걸 보여주기 위한 가상의 상황이에요.
기본키 — 한 행을 정확히 찾는 대표 값
우리 members 테이블에서는 member_id가 기본키입니다.
| 🔑 member_id | member_name | gender | age | joined_at |
|---|---|---|---|---|
| 1 | 김철수 | M | 35 | 2025-01-15 |
| 2 | 이영희 | F | 28 | 2025-02-03 |
| 3 | 박민수 | M | 42 | 2025-02-10 |
| ⋮ | ||||
| 20 | 백서윤 | F | 21 | 2026-04-02 |
이름이나 전화번호는 다른 회원과 같을 수도 있고, 나중에 바뀔 수도 있습니다. 하지만 각 행에 서로 다른 PK 값을 지정해두면 — 이름이 같은 회원이 있어도 PK 값으로 원하는 행 하나를 정확히 찾을 수 있어요.
기본키의 두 가지 규칙 — 중복 불가 · NULL 불가
기본키에는 데이터베이스가 직접 지키게 하는 두 가지 규칙이 있습니다.
members에 새 회원을 저장한다고 해봅시다. 이미 1번부터 20번까지 저장돼 있고, member_id 는 자동 번호 매기기를 잠시 꺼둔 채 직접 지정한다고 가정할게요.
| member_id 에 넣은 값 | 결과 | 이유 |
|---|---|---|
| 21 | 저장 성공 ✓ | 아직 아무도 쓰지 않은 번호입니다 |
| 1 | 저장 실패 ❌ | 이미 김철수가 쓰고 있습니다 — 중복 불가 |
| (비움 · NULL) | 저장 실패 ❌ | 기본키는 비울 수 없습니다 — NULL 불가 |
두 번째 규칙은 3-2에서 배운 NULL과 이어집니다. NULL은 현재 기록된 값이 없다는 뜻이었죠. 기본키 값이 없다면 그 행을 정확히 찾을 방법 자체가 사라집니다. 그래서 저장을 아예 허용하지 않아요.
PRIMARY KEY 로 지정한 컬럼은 자동으로 NULL 을 허용하지 않습니다.
우리 shop 스키마가 member_id INT NOT NULL 처럼 NOT NULL 을 함께 적어둔
건 — 읽는 사람이 규칙을 한눈에 보게 하려는 것이지, 그걸 적어야만 규칙이
생기는 건 아닙니다.
기본키는 하나, 구성 컬럼은 1개일 수도 N개일 수도
여기서 꼭 구분해야 할 것이 있습니다. 아마 이 강의에서 가장 자주 헷갈리는 지점일 거예요.
한 테이블에는 기본키를 하나만 지정할 수 있습니다. 하지만 그 기본키를 구성하는 컬럼이 꼭 하나여야 하는 건 아닙니다.
기본키를 만드는 방법은 두 가지 — 둘 다 기본키는 하나입니다.
예시members 의 member_id
행을 정하는 법번호 하나만 알면 행이 정해진다
기본키 개수1개
예시order_items 의 order_id + product_id
행을 정하는 법두 값을 한 쌍으로 묶어야 행이 정해진다
기본키 개수1개 — 구성 컬럼만 2개
달라지는 건 기본키의 개수가 아니라 — 그 하나를 만드는 컬럼의 개수입니다.
테이블 정의에 적히는 줄을 보면 더 분명합니다. 어느 쪽이든 PRIMARY KEY는 한 줄, 괄호도 하나예요.
기본키가 N개라는 뜻이 아닙니다. 기본키는 하나이고, 그 하나를 구성하는 컬럼이 N개일 수 있다는 뜻입니다.
이렇게 여러 컬럼이 함께 하나의 기본키를 만들면, 그걸 복합키라고 부릅니다.
복합키 읽기 — order_items 의 (order_id, product_id)
order_items, 즉 주문상세 테이블의 한 행에는 어떤 주문에 어떤 상품이 몇 개 들어 있는지를 기록합니다.
| order_id | product_id | quantity |
|---|---|---|
| 1 | 1 | 1 |
| 1 | 16 | 2 |
| 2 | 22 | 1 |
| 3 | 9 | 1 |
| 3 | 11 | 1 |
여기서 어떤 컬럼이 기본키가 될 수 있을지 하나씩 대입해 보겠습니다. 판단 기준은 방금 배운 중복 불가 하나면 충분해요.
| 이 기준으로 보면 | 실제 데이터에서는 |
|---|---|
| order_id 만 본다 | 한 주문에 여러 상품 → 1 이 두 행에 중복된다 ❌ |
| product_id 만 본다 | 같은 상품이 여러 주문에 → 9 가 3·6·12번 주문에 모두 있다 ❌ |
| 두 컬럼을 한 쌍으로 묶는다 | (1,1) · (1,16) · (2,22) · (3,9) · (3,11) — 모두 다르다 ✓ |
order_id만 보면 한 주문에 상품이 여러 개 들어가니 값이 중복됩니다. product_id만 봐도 같은 상품이 여러 주문에 들어가니 역시 중복돼요. 하지만 둘을 한 쌍으로 묶으면 각 행을 구별할 수 있습니다.
그래서 order_items의 기본키는 (order_id, product_id)입니다. 다시 한 번 — 기본키가 두 개인 게 아니라, 하나의 기본키를 두 컬럼이 함께 구성하는 겁니다.
같은 주문에 같은 상품은 한 줄만
복합키를 정하는 순간 규칙이 하나 따라옵니다. 같은 주문과 같은 상품의 조합은 두 번 저장할 수 없다는 것.
1번 주문에 이미 라운드 티셔츠(product_id 1)가 들어 있다면, 같은 주문에 라운드 티셔츠 행을 하나 더 만들 수 없습니다. 그럼 세 개를 산 경우는 어떻게 기록할까요?
| 이렇게 저장하려 하면 | 결과 |
|---|---|
| (1, 1) 행을 세 줄 만든다 | 저장 실패 ❌ — 같은 조합이 이미 있습니다 |
| (1, 1) 행 하나에 quantity = 3 | 저장 성공 ✓ |
실제 shop 데이터에도 이런 행이 있습니다. 10번 주문의 (10, 16) 은 quantity가 3이에요. 프리미엄 그래놀라를 세 개 산 주문이지만 행은 하나입니다.
이름으로 추측하지 말고 설정을 확인하세요
마지막으로 흔한 함정 하나. 컬럼 이름이 id로 끝난다고 무조건 기본키인 것은 아닙니다.
orders 테이블에는 order_id와 member_id가 둘 다 있습니다. 하지만 기본키는 order_id 하나뿐이에요.
| 🔑 order_id | member_id | ordered_at |
|---|---|---|
| 1 | 1 | 2025-02-01 |
| 6 | 1 | 2025-04-10 |
| 14 | 1 | 2025-08-01 |
| 20 | 1 | 2025-10-12 |
| 27 | 1 | 2025-12-10 |
| 34 | 1 | 2026-02-15 |
한 회원이 여러 번 주문하니까 member_id는 당연히 반복됩니다. 이름이 id로 끝난다는 건 아무 근거가 되지 못해요.
한눈에 정리
| 개념 | 한 줄 정의 | shop 에서는 |
|---|---|---|
| 기본키 (PK) | 각 행을 중복 없이 구별하는 대표 값 | members.member_id |
| 중복 불가 | 같은 PK 값을 두 행에 저장할 수 없다 | member_id 1번은 김철수 한 명 |
| NULL 불가 | PK 값을 비운 채로는 저장할 수 없다 | 비우면 그 행을 찾을 수 없으니까 |
| 기본키는 하나 | 한 테이블에 PK 는 하나만 지정한다 | 예외 없음 |
| 구성 컬럼은 1 ~ N개 | 그 하나를 만드는 컬럼은 여러 개일 수 있다 | (order_id, product_id) |
| 복합키 | 여러 컬럼이 함께 만든 하나의 PK | order_items |
| 이름 ≠ 기본키 | id 로 끝나도 PK 가 아닐 수 있다 | orders.member_id |
오늘의 핵심은 간단합니다 — 기본키는 테이블의 한 행을 정확히 찾는 값이며, 중복과 NULL을 허용하지 않습니다.
그런데 방금 본 orders.member_id는 그럼 뭘까요? 기본키도 아닌데 왜 거기 있을까요? 그건 다른 테이블의 기본키를 가리키는 값입니다. 그 이야기가 다음 강의예요.