후보키·기본키·외래키 차이: 데이터 무결성을 지키는 키 설계


1장. 전화번호를 회원의 기본키로 사용하면 안 될까#

새로운 회원 시스템을 만든다고 생각해 보겠습니다.

회원에게 다음과 같은 정보가 있습니다.

전화번호 이름 이메일
010-1111-2222 김하늘 sky@example.com
010-3333-4444 이바다 sea@example.com

현재 데이터만 보면 전화번호가 회원을 완벽하게 구별하는 것처럼 보입니다.

그렇다면 굳이 별도의 회원번호를 만들지 않고 전화번호를 기본키로 사용하면 어떨까요?

처음에는 꽤 좋은 생각처럼 보입니다.

전화번호는 이미 존재하고, 사용자도 쉽게 이해하며, 현재 데이터에서는 중복도 없습니다.

하지만 시간이 지나면 문제가 생깁니다.

김하늘 고객이 전화번호를 바꿨습니다.

010-1111-2222
        ↓
010-5555-6666

회원 테이블만 바꾸면 끝일까요?

그 회원을 참조하고 있는 주문, 상담, 쿠폰, 배송지, 결제 기록 등에도 기존 전화번호가 들어 있다면 관련 데이터를 모두 변경해야 할 수 있습니다.

더 큰 문제도 있습니다.

가족이 하나의 전화번호를 공동 연락처로 사용할 수도 있습니다.

기존 번호를 해지한 뒤 통신사가 그 번호를 다른 사람에게 다시 부여할 수도 있습니다.

전화번호는 사람을 설명하는 중요한 속성이지만 사람 그 자체를 안정적으로 식별하는 값이라고 보장하기는 어렵습니다.

여기서 데이터베이스 키 설계의 핵심이 드러납니다.

키를 선택할 때 중요한 것은 지금 데이터가 우연히 겹치지 않는지가 아닙니다.

앞으로 데이터가 추가되고 변경되더라도 그 값을 식별자로 사용할 수 있는가?

이 질문에 답해야 합니다.


2장. 데이터베이스에서 키가 필요한 이유#

데이터베이스에서는 수많은 행이 저장됩니다.

회원 테이블에 다음과 같은 데이터가 있다고 하겠습니다.

회원ID 이름 지역
M001 김하늘 서울
M002 이바다 부산
M003 김하늘 대전

이름이 김하늘인 사람이 두 명 있습니다.

따라서 다음 질문에는 이름만으로 답할 수 없습니다.

김하늘 회원의 주문을 조회해 주세요.

어느 김하늘인지 알 수 없기 때문입니다.

반면 회원ID를 사용하면 명확합니다.

M001

와

M003

은 서로 다른 회원입니다.

이처럼 데이터베이스에서 키는 행을 식별하거나 다른 테이블의 행과 관계를 맺는 데 사용하는 속성 또는 속성의 집합입니다.

키가 필요한 이유는 단순합니다.

데이터가 많아질수록 사람의 눈으로 행을 구분할 수 없기 때문입니다.


3장. 키의 핵심은 유일성과 최소성이다#

키를 이해할 때 가장 중요한 두 개념은 다음과 같습니다.

  • 유일성
  • 최소성

이 두 개를 정확히 구분하면 슈퍼키와 후보키를 어렵지 않게 이해할 수 있습니다.


3.1 유일성이란 무엇인가#

유일성은 특정 속성 또는 속성들의 조합으로 각 행을 서로 구별할 수 있다는 의미입니다.

다음 학생 데이터를 보겠습니다.

학번 학교메일 이름 학과
S001 s001@school.ac.kr 김하늘 컴퓨터공학
S002 s002@school.ac.kr 이바다 컴퓨터공학
S003 s003@school.ac.kr 김하늘 수학

학번은 모든 학생이 다릅니다.

따라서 학번만 알면 특정 학생 한 명을 찾을 수 있습니다.

S001 → 김하늘
S002 → 이바다
S003 → 김하늘

학교메일도 학생마다 하나만 부여된다는 규칙이 있다면 학생을 식별할 수 있습니다.

반면 이름은 그렇지 않습니다.

김하늘이라는 학생이 두 명 있기 때문입니다.

김하늘 → S001 또는 S003

따라서 이름에는 유일성이 없습니다.


3.2 최소성이란 무엇인가#

이번에는 다음 조합을 보겠습니다.

{학번, 이름}

학생을 구별할 수 있을까요?

물론 가능합니다.

학번이 이미 유일하기 때문입니다.

하지만 여기에는 불필요한 값이 들어 있습니다.

이름을 제거해 보겠습니다.

{학번}

여전히 학생을 구별할 수 있습니다.

따라서 {학번, 이름}은 유일하지만 최소하지 않습니다.

최소성은 이렇게 이해하면 쉽습니다.

키를 구성하는 속성 가운데 하나라도 제거했을 때 더 이상 행을 유일하게 구별할 수 없어야 한다.

최소성은 문자열 길이가 짧다는 의미가 아닙니다.

저장 공간이 작다는 의미도 아닙니다.

식별에 불필요한 속성이 포함되어 있지 않은가를 판단하는 조건입니다.


4장. 슈퍼키란 무엇인가#

슈퍼키는 하나의 행을 유일하게 식별할 수 있는 속성 또는 속성 집합입니다.

앞의 학생 테이블을 다시 보겠습니다.

{학번}

은 학생을 구별합니다.

따라서 슈퍼키입니다.

{학교메일}

도 학생을 구별하므로 슈퍼키입니다.

다음 조합도 학생을 구별합니다.

{학번, 이름}

학번이 이미 유일하기 때문입니다.

따라서 이것도 슈퍼키입니다.

심지어 다음도 가능합니다.

{학번, 이름, 학과}

마찬가지로 유일합니다.

즉 슈퍼키는 행을 식별할 수만 있다면 불필요한 속성이 포함되어 있어도 됩니다.

속성 집합 유일성 슈퍼키 여부
{학번} 있음 예
{학교메일} 있음 예
{학번, 이름} 있음 예
{학번, 이름, 학과} 있음 예
{이름} 없음 아니오

이 때문에 슈퍼키만으로는 실제 기본키 후보를 정하기 어렵습니다.

그래서 최소성이라는 조건이 추가됩니다.


5장. 후보키는 유일성과 최소성을 모두 만족해야 한다#

후보키는 슈퍼키 가운데 최소성을 만족하는 키입니다.

즉 다음 두 조건을 동시에 만족해야 합니다.

유일성
+
최소성
=
후보키

학생 테이블에서 학번 하나만으로 학생을 식별할 수 있습니다.

{학번}

여기서 제거할 속성도 없습니다.

따라서 후보키입니다.

학교메일도 학생마다 반드시 하나이며 중복되지 않는다는 업무 규칙이 있다면 다음 역시 후보키가 될 수 있습니다.

{학교메일}

반면 다음은 후보키가 아닙니다.

{학번, 이름}

유일성은 있지만 이름을 제거해도 학번만으로 학생을 식별할 수 있기 때문입니다.

따라서 최소성이 없습니다.

정리하면 다음과 같습니다.

속성 집합 유일성 최소성 분류
{학번} 있음 있음 후보키
{학교메일} 있음 있음 후보키
{학번, 이름} 있음 없음 슈퍼키
{이름} 없음 없음 키가 아님

모든 후보키는 슈퍼키입니다.

하지만 모든 슈퍼키가 후보키인 것은 아닙니다.

후보키 ⊆ 슈퍼키

6장. 현재 데이터가 유일하다고 후보키가 되는 것은 아니다#

여기서 매우 중요한 함정이 있습니다.

현재 학생 데이터가 다음과 같다고 하겠습니다.

이름 학과
김하늘 컴퓨터공학
이바다 컴퓨터공학
김하늘 수학

현재 데이터에서는 다음 조합이 모두 다릅니다.

김하늘 + 컴퓨터공학
이바다 + 컴퓨터공학
김하늘 + 수학

그렇다면 (이름, 학과)를 후보키로 사용해도 될까요?

그렇지 않습니다.

다음 학기에 같은 컴퓨터공학과에 김하늘이라는 학생이 또 입학할 수 있습니다.

김하늘 | 컴퓨터공학
김하늘 | 컴퓨터공학

즉 현재 데이터에서는 우연히 유일할 뿐입니다.

키의 유일성은 현재 몇 개 행에서 중복이 발견되지 않았다는 뜻이 아닙니다.

업무 규칙상 앞으로 들어올 데이터에서도 같은 값을 가질 수 없어야 합니다.

따라서 후보키를 판단할 때는 현재 데이터보다 업무 규칙이 더 중요합니다.


7장. 기본키는 후보키 가운데 대표로 선택한 하나다#

후보키가 여러 개 존재할 수 있습니다.

학생 테이블을 예로 들어보겠습니다.

후보키 1: 학번
후보키 2: 학교메일

둘 모두 학생을 유일하게 식별할 수 있습니다.

이 가운데 하나를 대표 식별자로 선택합니다.

이것이 기본키입니다.

예를 들어 학번을 기본키로 선택할 수 있습니다.

PRIMARY KEY = 학번

그러면 학교메일은 어떻게 될까요?

후보키의 자격을 잃는 것은 아닙니다.

기본키로 선택되지 않았을 뿐입니다.

이처럼 후보키 가운데 기본키로 선택되지 않은 키를 대체키라고 합니다.

후보키
 ├─ 기본키
 └─ 대체키

학생 사례에서는 다음과 같습니다.

기본키: 학번
대체키: 학교메일

학교메일에 유일성이 반드시 필요하다면 UNIQUE 제약을 둘 수 있습니다.

CREATE TABLE student (
    student_id VARCHAR(20) PRIMARY KEY,
    school_email VARCHAR(200) NOT NULL UNIQUE,
    name VARCHAR(100) NOT NULL
);

8장. 좋은 기본키는 단순히 유일한 값이 아니다#

후보키 가운데 기본키를 선택할 때는 유일성만 보는 것으로 충분하지 않습니다.

가능하면 다음과 같은 특성을 함께 고려하는 것이 좋습니다.

  • 값이 자주 바뀌지 않는가
  • 값이 너무 길지 않은가
  • 개인정보나 민감정보를 포함하지 않는가
  • 업무 규칙이 바뀌어도 유지할 수 있는가
  • 다른 테이블에서 참조하기 쉬운가

전화번호가 대표적인 예입니다.

전화번호는 현재 유일할 수 있습니다.

그러나 바뀔 수 있습니다.

이메일 역시 변경될 수 있습니다.

회원 로그인 ID도 정책에 따라 사용자가 변경할 수 있습니다.

반면 시스템에서 발급한 회원ID는 다음처럼 유지할 수 있습니다.

M000001
M000002
M000003

김하늘 회원이 전화번호를 바꾸더라도 회원ID는 그대로 유지됩니다.

회원ID: M000001

전화번호
010-1111-2222
        ↓
010-5555-6666

주문은 계속 같은 회원ID를 참조합니다.

이렇게 식별자와 변경 가능한 속성을 분리하면 데이터 관계를 훨씬 안정적으로 유지할 수 있습니다.


9장. 복합키는 여러 속성을 묶어 하나의 키로 사용하는 방식이다#

항상 한 개의 속성만으로 행을 식별할 수 있는 것은 아닙니다.

학생의 수강 정보를 생각해 보겠습니다.

학번 과목코드 성적
S001 DB101 A
S001 OS201 B
S002 DB101 B

학번만으로는 수강 행을 구별할 수 없습니다.

학생 S001이 여러 과목을 수강하기 때문입니다.

과목코드만으로도 구별할 수 없습니다.

DB101을 여러 학생이 수강하기 때문입니다.

하지만 다음 두 값을 함께 사용하면 행을 구별할 수 있습니다.

학번 + 과목코드

따라서 다음 조합을 키로 사용할 수 있습니다.

(S001, DB101)
(S001, OS201)
(S002, DB101)

이처럼 두 개 이상의 속성으로 구성된 키를 복합키라고 합니다.


10장. 복합키도 업무 규칙이 바뀌면 바뀔 수 있다#

수강 테이블의 기본키를 다음처럼 정했다고 하겠습니다.

(학번, 과목코드)

그런데 재수강을 허용하기로 했습니다.

학생 S001이 DB101을 두 번 수강합니다.

학번 과목코드 학기 성적
S001 DB101 2026-1 B
S001 DB101 2026-2 A

이제 (학번, 과목코드)만으로는 두 행을 구별할 수 없습니다.

따라서 다음과 같이 확장할 수 있습니다.

(학번, 과목코드, 학기)

또는 별도의 수강ID를 만들 수도 있습니다.

수강ID

여기서 중요한 것은 복합키 자체가 좋은지 나쁜지가 아닙니다.

한 행이 어떤 사건을 표현하는지에 따라 키가 달라진다는 점입니다.

한 행이 다음을 의미한다면:

학생 한 명이 과목 하나를 수강한 사실

학번 + 과목코드가 충분할 수 있습니다.

하지만 한 행이 다음을 의미한다면:

학생이 특정 학기에 특정 과목을 수강한 사실

학기까지 필요합니다.

키는 데이터의 의미를 반영해야 합니다.


11장. 외래키는 행을 식별하는 키가 아니라 관계를 연결하는 키다#

이번에는 회원과 주문을 생각해 보겠습니다.

회원 테이블:

회원ID 이름
M001 김하늘
M002 이바다

주문 테이블:

주문ID 회원ID 금액
O101 M001 30000
O102 M001 15000
O103 M002 40000

주문 테이블의 기본키는 주문ID입니다.

O101
O102
O103

각 주문을 구별하기 때문입니다.

그런데 주문 테이블에 회원ID도 있습니다.

이 회원ID는 주문을 구별하기 위한 것이 아닙니다.

누가 주문했는지를 나타내기 위해 회원 테이블의 회원을 가리킵니다.

이것이 외래키입니다.

주문.회원ID
        ↓
회원.회원ID

SQL에서는 다음처럼 정의할 수 있습니다.

CREATE TABLE orders (
    order_id VARCHAR(20) PRIMARY KEY,
    member_id VARCHAR(20) NOT NULL,

    FOREIGN KEY (member_id)
        REFERENCES member(member_id)
);

외래키의 핵심 역할은 테이블 사이의 연결을 유지하는 것입니다.


12장. 외래키는 중복될 수 있다#

외래키를 처음 배우면 키라는 단어 때문에 유일해야 한다고 생각하기 쉽습니다.

하지만 외래키는 중복될 수 있습니다.

다음 데이터를 다시 보겠습니다.

주문ID 회원ID
O101 M001
O102 M001
O103 M002

회원ID M001이 두 번 등장합니다.

정상입니다.

M001 회원이 주문을 두 번 했기 때문입니다.

M001
 ├─ O101
 └─ O102

외래키는 부모 테이블의 행을 가리키는 값입니다.

자식 테이블에서는 여러 행이 같은 부모를 참조할 수 있습니다.

따라서 외래키는 기본적으로 유일성을 의미하지 않습니다.


13장. 기본키와 외래키의 차이#

회원과 주문 사례로 비교하면 명확합니다.

구분 회원ID 주문ID
회원 테이블 기본키 없음
주문 테이블 외래키 기본키

회원ID는 회원 테이블에서는 회원을 식별합니다.

따라서 기본키입니다.

하지만 주문 테이블에서는 주문한 회원을 가리킵니다.

따라서 외래키입니다.

같은 속성이 어느 테이블에 있느냐에 따라 역할이 달라질 수 있습니다.

회원.회원ID
→ 회원을 식별하는 기본키

주문.회원ID
→ 회원을 참조하는 외래키

키의 이름보다 그 테이블에서 어떤 역할을 하는가를 보는 것이 중요합니다.


14장. 개체 무결성은 식별할 수 없는 행을 막는다#

기본키가 존재하는 가장 중요한 이유 중 하나는 행을 확실하게 식별하기 위해서입니다.

주문 테이블이 다음과 같다고 하겠습니다.

주문ID 회원ID
O101 M001
O102 M002

여기에 다음 행을 넣는다고 생각해 보겠습니다.

NULL | M001

주문ID가 없습니다.

이 주문을 나중에 어떻게 정확하게 찾을 수 있을까요?

또 다음과 같은 행이 들어온다면 어떨까요?

O101 | M002

이미 O101이라는 주문이 존재합니다.

어느 것이 진짜 O101인지 구별할 수 없습니다.

그래서 기본키에는 중요한 규칙이 적용됩니다.

  • 기본키 값은 중복될 수 없다.
  • 기본키 값은 NULL일 수 없다.

이 원칙을 개체 무결성이라고 합니다.

PRIMARY KEY
=
UNIQUE
+
NOT NULL

개념적으로 이렇게 이해할 수 있습니다.


15장. 복합 기본키에서는 조합 전체가 식별자다#

다음 수강 테이블의 기본키가 (학번, 과목코드)라고 하겠습니다.

학번 과목코드
S001 DB101
S001 OS201
S002 DB101

학번 S001이 두 번 나옵니다.

문제가 아닙니다.

과목코드 DB101도 두 번 나옵니다.

역시 문제가 아닙니다.

중복되면 안 되는 것은 전체 조합입니다.

S001 + DB101

이 조합이 다시 들어오는 것은 허용되지 않습니다.

S001 | DB101
S001 | DB101

즉 복합 기본키에서는 각각의 열이 따로 유일할 필요가 없습니다.

구성 속성 전체의 조합이 유일해야 합니다.


16장. 참조 무결성은 존재하지 않는 데이터를 가리키는 것을 막는다#

회원 테이블에 다음 두 회원만 있다고 하겠습니다.

M001
M002

그런데 주문 테이블에 다음 주문을 넣으려고 합니다.

O104 | M999

M999라는 회원은 존재하지 않습니다.

외래키 제약이 없다면 데이터베이스는 이 값을 그대로 저장할 수도 있습니다.

그러면 시스템에는 다음과 같은 이상한 상태가 생깁니다.

주문은 있는데 주문한 회원은 존재하지 않는다.

외래키를 설정하면 이런 입력을 막을 수 있습니다.

주문.회원ID = M999

        ↓

회원 테이블 검색

        ↓

M999 없음

        ↓

입력 거부

이처럼 외래키가 존재하는 부모 행을 참조하도록 유지하는 규칙을 참조 무결성이라고 합니다.


17장. 외래키만 걸면 끝나는 것은 아니다#

외래키를 만들었다고 모든 관계 규칙이 해결되는 것은 아닙니다.

예를 들어 주문의 회원ID가 NULL이어도 되는 구조라면 다음 데이터가 들어갈 수 있습니다.

O105 | NULL

외래키 관점에서는 참조할 값 자체가 없으므로 허용될 수 있습니다.

하지만 업무 규칙이 다음과 같다면 문제가 됩니다.

모든 주문은 반드시 회원에게 속해야 한다.

이 경우 외래키뿐 아니라 NOT NULL도 필요합니다.

member_id VARCHAR(20) NOT NULL

즉 다음 두 조건은 서로 다릅니다.

FOREIGN KEY
→ 값이 있다면 존재하는 회원을 가리켜야 한다.

NOT NULL
→ 반드시 값이 있어야 한다.

둘을 함께 사용해야 업무 규칙을 정확하게 표현할 수 있는 경우가 많습니다.


18장. 부모 데이터를 삭제하면 자식 데이터는 어떻게 해야 할까#

회원 M001에게 주문이 두 건 있다고 하겠습니다.

회원 M001
 ├─ 주문 O101
 └─ 주문 O102

이 상태에서 회원 M001을 삭제하면 어떻게 해야 할까요?

정답은 하나가 아닙니다.

업무에 따라 달라집니다.

대표적인 정책은 다음과 같습니다.

삭제 거부#

회원에게 주문이 남아 있다면 회원 삭제를 막습니다.

ON DELETE RESTRICT

거래 기록을 반드시 보존해야 하는 시스템에서 고려할 수 있습니다.

연결된 자식도 함께 삭제#

회원이 삭제되면 주문까지 모두 삭제합니다.

ON DELETE CASCADE

일부 종속 데이터에서는 유용할 수 있지만 거래 기록처럼 보존해야 하는 데이터에 무조건 적용하면 위험합니다.

외래키를 NULL로 변경#

회원은 삭제하지만 주문은 남깁니다.

ON DELETE SET NULL

이 경우 외래키 열이 NULL을 허용해야 합니다.

중요한 것은 SQL 옵션 자체가 아닙니다.

다음 질문을 먼저 해야 합니다.

회원이 사라졌을 때 과거 주문 기록은 어떤 상태로 남아야 하는가?

기술적인 삭제 옵션은 업무 정책을 구현하는 수단일 뿐입니다.


19장. 회원 탈퇴와 회원 삭제는 같은 의미가 아닐 수 있다#

실제 서비스에서는 회원 탈퇴를 곧바로 데이터 삭제와 동일하게 처리하면 문제가 생길 수 있습니다.

회원에게 다음 기록이 남아 있다고 하겠습니다.

  • 완료 주문
  • 환불 기록
  • 세금·회계 관련 기록
  • 상담 이력

회원 행을 완전히 삭제하면서 주문까지 연쇄 삭제한다면 필요한 거래 기록이 사라질 수 있습니다.

그래서 실제 시스템에서는 다음처럼 계정 상태를 변경하는 방식을 사용할 수도 있습니다.

회원상태 = 탈퇴

또는 필요한 개인정보를 별도로 처리하면서 거래 식별 관계는 유지할 수도 있습니다.

여기서 중요한 것은 데이터베이스 키 설계가 단순히 PRIMARY KEY와 FOREIGN KEY 문법을 정하는 일이 아니라는 점입니다.

데이터가 생성되고 변경되고 사라지는 생명주기까지 고려해야 합니다.


20장. 도메인 무결성은 값 자체가 올바른지 확인한다#

기본키와 외래키가 모두 정상이라고 해서 데이터 전체가 올바른 것은 아닙니다.

다음 주문상품 데이터가 있다고 하겠습니다.

주문ID 상품ID 수량
O101 P001 -5

주문ID도 존재합니다.

상품ID도 존재합니다.

키 관계에는 문제가 없습니다.

하지만 수량이 -5입니다.

업무적으로 말이 되지 않습니다.

이런 값의 범위와 형식을 관리하는 것이 도메인 무결성입니다.

예를 들어 다음과 같은 제약을 사용할 수 있습니다.

CHECK (quantity >= 1)

주문 상태도 마찬가지입니다.

허용 상태가 다음 세 개라고 하겠습니다.

접수
발송
완료

그러면 다음처럼 제한할 수 있습니다.

CHECK (
    status IN ('접수', '발송', '완료')
)

도메인 무결성은 해당 열에 업무적으로 허용된 값만 들어오도록 만드는 역할을 합니다.


21장. DEFAULT는 검증 규칙이 아니다#

다음 정의를 생각해 보겠습니다.

status VARCHAR(20) DEFAULT '접수'

상태 값을 입력하지 않으면 접수가 들어갑니다.

하지만 다음 값을 직접 입력하면 어떨까요?

TEST

자료형이 VARCHAR라면 저장될 수 있습니다.

DEFAULT는 값이 생략됐을 때 기본값을 제공할 뿐입니다.

잘못된 값을 막는 제약은 아닙니다.

따라서 허용 상태를 제한하려면 별도의 검증 규칙이 필요합니다.

CHECK (
    status IN ('접수', '발송', '완료')
)

즉 다음 두 개념은 다릅니다.

DEFAULT
→ 값이 없을 때 무엇을 넣을 것인가

CHECK
→ 어떤 값을 허용할 것인가

22장. 세 가지 대표 무결성을 구분해 보기#

데이터베이스 무결성은 한 종류가 아닙니다.

대표적으로 다음과 같이 구분할 수 있습니다.

무결성 보호 대상 대표 방법
개체 무결성 행의 식별 PRIMARY KEY
참조 무결성 테이블 사이 관계 FOREIGN KEY
도메인 무결성 값의 범위와 형식 타입, NOT NULL, CHECK

예를 들어 세 가지 잘못된 주문이 들어온다고 하겠습니다.

사례 1#

NULL | M001 | 10000

주문ID가 없습니다.

개체 무결성 문제입니다.

사례 2#

O102 | M999 | 10000

존재하지 않는 회원 M999를 가리킵니다.

참조 무결성 문제입니다.

사례 3#

O103 | M001 | -10000

금액이 음수입니다.

도메인 또는 업무 규칙 문제입니다.

모두 잘못된 데이터이지만 막아야 하는 제약은 서로 다릅니다.


23장. 키를 함수 종속으로 확인하는 방법#

후보키를 판단할 때 함수 종속을 사용할 수도 있습니다.

다음 릴레이션이 있다고 하겠습니다.

학생(
    학번,
    학교메일,
    이름,
    학과
)

업무 규칙이 다음과 같다고 가정합니다.

학번 → 학교메일, 이름, 학과

학교메일 → 학번, 이름, 학과

학번 하나를 알면 모든 속성을 결정할 수 있습니다.

학교메일 하나를 알아도 모든 속성을 결정할 수 있습니다.

따라서 각각 후보키가 될 수 있습니다.

{학번}
{학교메일}

하지만 다음 조합은 어떨까요?

{학번, 이름}

전체 속성을 결정할 수 있습니다.

그러나 이름을 제거해도 학번 하나만으로 전체 속성을 결정할 수 있습니다.

따라서 최소성이 없습니다.

후보키가 아니라 슈퍼키입니다.


24장. 함수 종속이 부족하면 후보키를 마음대로 추정하면 안 된다#

다음 함수 종속만 주어졌다고 하겠습니다.

학번 → 이름, 학과

주민번호 → 이름, 전화번호

릴레이션은 다음과 같습니다.

R(
    학번,
    주민번호,
    이름,
    학과,
    전화번호
)

여기서 학번 하나만 가지고 전체 속성을 결정할 수 있을까요?

아닙니다.

학번으로 알 수 있는 것은 다음뿐입니다.

학번
이름
학과

주민번호와 전화번호는 결정할 수 없습니다.

주민번호도 마찬가지입니다.

주민번호
이름
전화번호

학번과 학과를 결정할 수 없습니다.

따라서 주어진 함수 종속만 놓고 보면 학번이나 주민번호를 각각 후보키라고 바로 결론 내릴 수 없습니다.

두 값을 함께 사용하면 다음과 같습니다.

{학번, 주민번호}

모든 속성을 결정할 수 있으므로 후보키가 될 가능성이 있습니다.

후보키를 계산할 때 중요한 것은 상식적으로 유일해 보이는 값이 아니라 주어진 업무 규칙과 함수 종속입니다.


25장. 자연키와 인조키를 어떻게 볼 것인가#

키를 설계할 때 자주 등장하는 구분이 있습니다.

자연키#

업무에서 이미 의미를 가진 값을 키로 사용합니다.

예를 들어:

상품코드
사업자번호
국가코드

인조키#

시스템에서 식별 목적으로 별도로 만든 값을 사용합니다.

예를 들어:

member_id
order_id
product_id

자동 증가 숫자나 UUID 등이 사용될 수 있습니다.

인조키의 장점은 업무 속성이 바뀌더라도 식별자를 유지하기 쉽다는 점입니다.

하지만 인조키를 만들었다고 모든 중복 문제가 자동으로 해결되는 것은 아닙니다.

예를 들어 회원ID를 자동으로 발급한다면 같은 사람이 여러 번 가입해도 다음처럼 저장될 수 있습니다.

M001 | 김하늘 | 010-1111-2222

M982 | 김하늘 | 010-1111-2222

회원ID는 서로 다르므로 기본키 위반은 아닙니다.

하지만 업무적으로 동일한 회원의 중복 가입을 막아야 한다면 별도의 규칙이 필요합니다.

즉 다음 두 문제는 다릅니다.

행을 어떻게 식별할 것인가

현실의 같은 대상을 어떻게 중복 판별할 것인가

인조키는 첫 번째 문제를 해결하지만 두 번째 문제까지 자동으로 해결하지는 않습니다.


26장. 전화번호에 UNIQUE를 걸면 해결될까#

회원ID를 기본키로 만들고 전화번호에는 UNIQUE를 설정하는 방법도 생각할 수 있습니다.

CREATE TABLE member (
    member_id BIGINT PRIMARY KEY,
    phone VARCHAR(30) UNIQUE
);

하지만 이 설계가 항상 맞는 것은 아닙니다.

가족이 같은 대표 전화번호를 사용한다면 정상 회원 가입을 막을 수 있습니다.

반대로 사업 정책상 반드시 한 전화번호당 하나의 회원만 허용한다면 유일 제약이 적절할 수 있습니다.

즉 UNIQUE는 기술적으로 좋아 보인다는 이유로 추가하는 것이 아닙니다.

먼저 업무 규칙을 확인해야 합니다.

같은 전화번호를 두 회원이 사용할 수 있는가?

가능하다면 유일 제약을 둘 수 없습니다.

불가능하다고 정의했다면 데이터베이스에서도 그 규칙을 강제하는 것이 좋습니다.


27장. 키는 현재 데이터보다 미래의 변경에서 검증된다#

좋은 키인지 확인하려면 정상적인 현재 데이터만 보는 것보다 변경 상황을 만들어 보는 것이 효과적입니다.

예를 들어 회원 테이블이 있다면 다음 질문을 해봅니다.

전화번호가 바뀌면?

이메일이 바뀌면?

동명이인이 가입하면?

같은 전화번호를 가족이 사용하면?

회원이 탈퇴하면?

탈퇴한 회원의 주문은 남겨야 하면?

기존 번호가 다른 사람에게 재할당되면?

수강 데이터라면 다음 질문을 할 수 있습니다.

재수강하면?

같은 학기에 같은 과목을 두 번 신청하면?

분반이 있다면?

수강 취소 이력을 보존해야 한다면?

키 설계는 첫 번째 행을 저장할 때보다 예외와 변경이 발생할 때 품질이 드러납니다.


28장. 실제 SQL로 키와 무결성을 구성해 보기#

회원과 주문을 다음처럼 정의할 수 있습니다.

CREATE TABLE member (
    member_id VARCHAR(20) PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(200) UNIQUE
);

회원ID는 기본키입니다.

member_id

따라서 중복과 NULL이 허용되지 않습니다.

주문은 다음과 같이 정의할 수 있습니다.

CREATE TABLE orders (
    order_id VARCHAR(20) PRIMARY KEY,
    member_id VARCHAR(20) NOT NULL,
    status VARCHAR(20) NOT NULL,
    amount INTEGER NOT NULL,

    FOREIGN KEY (member_id)
        REFERENCES member(member_id)
        ON DELETE RESTRICT,

    CHECK (
        status IN ('접수', '발송', '완료')
    ),

    CHECK (
        amount >= 0
    )
);

이 테이블에는 여러 종류의 규칙이 함께 들어 있습니다.

order_id
→ 주문 식별

member_id
→ 회원 참조

status
→ 허용 상태 제한

amount
→ 음수 금액 방지

데이터베이스 설계에서는 하나의 제약조건으로 모든 문제를 해결하려 하지 않습니다.

각 데이터가 가진 의미에 맞는 제약을 조합합니다.


29장. 잘못된 데이터를 일부러 넣어 보면 설계가 보인다#

좋은 설계인지 확인하려면 정상 데이터만 넣어보는 것으로 부족합니다.

잘못된 입력도 시도해 봐야 합니다.

중복 기본키#

INSERT INTO orders
VALUES ('O101', 'M001', '접수', 10000);

INSERT INTO orders
VALUES ('O101', 'M002', '접수', 20000);

두 번째 O101은 거부되어야 합니다.

존재하지 않는 회원#

INSERT INTO orders
VALUES ('O102', 'M999', '접수', 10000);

회원 M999가 없다면 거부되어야 합니다.

허용되지 않은 상태#

INSERT INTO orders
VALUES ('O103', 'M001', '분실', 10000);

분실이 허용 상태에 없다면 거부되어야 합니다.

음수 금액#

INSERT INTO orders
VALUES ('O104', 'M001', '접수', -100);

금액 제약에 의해 거부되어야 합니다.

이렇게 테스트하면 각 제약조건이 무엇을 보호하고 있는지 명확하게 확인할 수 있습니다.


30장. 키의 종류를 한 번에 정리하기#

키 종류 의미
슈퍼키 행을 유일하게 식별할 수 있는 속성 집합
후보키 유일성과 최소성을 모두 만족하는 슈퍼키
기본키 후보키 가운데 대표로 선택한 키
대체키 후보키 가운데 기본키로 선택되지 않은 키
외래키 다른 테이블의 키를 참조하는 속성
복합키 두 개 이상의 속성으로 구성된 키

관계는 다음처럼 이해할 수 있습니다.

슈퍼키
   ↑
후보키
 ├─ 기본키
 └─ 대체키

외래키는 이 계층과 목적이 다릅니다.

외래키의 역할은 행 자체를 식별하는 것이 아니라 다른 테이블과의 관계를 유지하는 것입니다.

복합키는 키의 역할이 아니라 키를 구성하는 속성의 개수를 설명합니다.

따라서 복합 후보키도 존재할 수 있고, 복합 기본키도 존재할 수 있으며, 복합 외래키도 존재할 수 있습니다.


31장. 좋은 키 설계는 데이터의 생명주기를 함께 본다#

키 설계는 단순히 중복되지 않는 값을 찾는 작업이 아닙니다.

실제 시스템에서는 다음 상황까지 생각해야 합니다.

값이 변경되면 어떻게 되는가

참조 중인 데이터는 어떻게 되는가

대상이 삭제되면 어떻게 되는가

과거 기록은 계속 유지해야 하는가

업무 규칙이 바뀌면 식별자는 유지되는가

전화번호는 지금 회원을 구별할 수 있어도 변경될 수 있습니다.

학교메일도 졸업이나 정책 변경으로 달라질 수 있습니다.

수강의 (학번, 과목코드)는 재수강이라는 요구가 생기는 순간 충분하지 않을 수 있습니다.

그래서 키를 선택할 때는 현재 표만 보지 말고 데이터가 앞으로 어떻게 살아 움직일지를 함께 봐야 합니다.


32장. 핵심 정리#

데이터베이스의 키는 행을 식별하고 데이터 사이의 관계를 유지하기 위한 핵심 장치입니다.

슈퍼키는 행을 유일하게 식별할 수 있는 속성 집합입니다.

후보키는 슈퍼키 가운데 유일성과 최소성을 모두 만족하는 키입니다.

기본키는 후보키 가운데 대표로 선택한 키입니다.

대체키는 후보키이지만 기본키로 선택되지 않은 키입니다.

외래키는 다른 테이블의 행을 참조하여 관계를 유지합니다.

복합키는 두 개 이상의 속성으로 구성된 키입니다.

그리고 키는 데이터 무결성과 직접 연결됩니다.

기본키
→ 개체 무결성

외래키
→ 참조 무결성

CHECK, 타입, NOT NULL 등
→ 도메인 무결성

무엇보다 중요한 것은 현재 데이터에서 우연히 중복되지 않는 값을 키로 선택하지 않는 것입니다.

전화번호가 오늘 모두 다르다고 해서 영원히 회원을 식별할 수 있는 것은 아닙니다.

학생의 이름과 학과 조합이 현재 유일하다고 해서 다음 학기에도 유일하다고 보장할 수는 없습니다.

키 설계에서 가장 중요한 질문은 이것입니다.

이 값은 데이터가 추가되고 변경되고 삭제되는 동안에도 같은 대상을 안정적으로 식별할 수 있는가?

그리고 외래키를 설계할 때는 한 가지 질문을 더 해야 합니다.

참조하던 대상이 변경되거나 사라졌을 때 이 관계는 어떻게 되어야 하는가?

이 두 질문에 명확하게 답할 수 있다면 기본키와 외래키는 단순한 SQL 문법이 아니라 데이터의 정체성과 관계를 지키는 설계 도구가 됩니다.

이 페이지의 목차