테이블에서 한 행을 유일하게 가리키는 속성(들). 관계 모델의 출발점이다.
키의 계보
슈퍼키 행을 유일하게 구분하는 속성 집합 (군더더기가 있어도 된다)
예: (회원ID, 이름) — 회원ID만으로 충분한데 이름이 붙어 있다
↓ 최소화
후보키 더 뺄 수 없는 슈퍼키. 여러 개일 수 있다
예: 회원ID · 이메일 · 주민번호
↓ 그중 하나를 고른다
기본키 설계자가 대표로 선택한 후보키. 테이블당 하나
대체키 선택되지 않은 나머지 후보키 → UNIQUE 제약으로 지킨다
기본키의 두 가지 강제 조건
CREATE TABLE member (
id BIGINT PRIMARY KEY, -- ① 유일하다 ② NULL 이 아니다
email VARCHAR(255) NOT NULL UNIQUE -- 대체키는 UNIQUE 로 지킨다
);
NULL을 허용하지 않는 이유가 중요하다. NULL은 "모름"이라 NULL끼리는 같은지 판단할 수 없다. 유일성을 검사할 수 없으니 키가 될 수 없다.
SELECT NULL = NULL; -- NULL (true 가 아니다!)
-- 그래서 UNIQUE 칼럼에는 NULL 이 여러 개 들어갈 수 있다 (PostgreSQL·MySQL 공통)
자연키 vs 대리키
자연키(natural key) 업무상 의미가 있는 값 — 이메일 · 사업자번호 · 주민번호
장점 추가 칼럼이 없다. 조인 없이 의미를 알 수 있다
단점 업무 규칙이 바뀌면 키가 바뀐다 (이메일 변경 → 참조하는 모든 곳이 흔들린다)
길면 인덱스가 커진다
대리키(surrogate key) 업무와 무관한 인공 값 — AUTO_INCREMENT · UUID
장점 절대 안 바뀐다. 작고 단조 증가라 인덱스에 유리하다
단점 자연키의 유일성을 UNIQUE 로 따로 걸어야 한다 (빠뜨리면 중복 데이터)
실무 기본값은 대리키 PK + 자연키 UNIQUE 조합이다.
CREATE TABLE member (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, -- PostgreSQL 표준 문법
email VARCHAR(255) NOT NULL,
UNIQUE (email) -- 자연키는 여기서 지킨다
);
MySQL은 id BIGINT AUTO_INCREMENT PRIMARY KEY를 쓴다.
PK 선택이 성능까지 바꾼다
-
InnoDB — PK 가 곧 데이터다(클러스터형)
- PK 가 크면 모든 보조 인덱스가 함께 커진다
- 랜덤 UUID 면 페이지 분할이 잦다
-
PostgreSQL — PK 도 별도 인덱스일 뿐이다(힙)
- 영향이 훨씬 작다
같은 "UUID를 PK로 쓰지 마라"는 조언이 엔진에 따라 강도가 다른 이유다.
확인하는 쿼리
-- PostgreSQL: 이 테이블의 PK 와 UNIQUE 제약
SELECT conname, contype, pg_get_constraintdef(oid)
FROM pg_constraint WHERE conrelid = 'member'::regclass AND contype IN ('p','u');
-- MySQL
SHOW KEYS FROM member WHERE Key_name = 'PRIMARY';
면접 함정
- ❌ "기본키는 항상 하나의 칼럼" → 복합키도 기본키가 될 수 있다(연결 테이블에서 흔하다).
- ❌ "UNIQUE면 기본키와 같다" → UNIQUE는 NULL을 허용하고 여러 개 걸 수 있다.
- ❌ "주민번호를 PK로" → 법적으로 저장 자체가 제한되고, 자연키의 단점(변경·길이)까지 다 갖는다.