정규화가 필요한 이유
테이블을 왜 이렇게 많이 쪼개놓았나요? — 정규화를 모르면 이 질문에 답할 수 없고, 데이터 이상 현상에 시달리게 됩니다. 정규화는 관계형 데이터베이스 설계의 근간이며, 좋은 테이블 구조와 나쁜 테이블 구조를 구분하는 기준입니다.
테이블 설계를 잘못하면 데이터의 중복이 발생하고, 중복은 이상 현상을 유발합니다. 정규화는 이 문제를 체계적으로 해결하는 방법론입니다. 1970년대 에드거 커드(Edgar F. Codd)가 제안한 이래, 오늘날까지 데이터베이스 설계의 표준으로 자리잡고 있습니다.
왜 하나의 테이블에 모든 것을 넣으면 안 되는가
초보 개발자는 종종 테이블 하나에 다 넣으면 편하지 않나?라고 생각합니다. 조인도 필요 없고, 쿼리도 간단해 보입니다. 하지만 이 접근은 데이터가 쌓이면서 심각한 문제를 일으킵니다.
┌──────────────────────────────────────────────────────┐
│ 하나의 테이블에 모든 정보를 넣으면... │
│ │
│ 1. 데이터 중복: 같은 정보가 여러 행에 반복 저장 │
│ → 저장 공간 낭비 │
│ → 수정 시 모든 중복 행을 업데이트해야 함 │
│ │
│ 2. 이상 현상: 삽입/삭제/갱신 시 의도치 않은 부작용 │
│ → 데이터 무결성 위반 │
│ → 비즈니스 로직 오류 │
│ │
│ 3. NULL 남발: 관련 없는 컬럼에 NULL이 대량 발생 │
│ → 의미 파악 어려움 │
│ → 저장 효율 저하 │
└──────────────────────────────────────────────────────┘이상 현상
하나의 테이블에 너무 많은 정보를 넣으면 이상 현상(Anomaly)이 발생합니다. 다음은 학생·과목·교수 정보를 하나의 테이블에 넣은 사례입니다.
┌────────┬────────┬──────────┬────────┬─────────┐
│ 학번 │ 이름 │ 과목명 │ 교수 │ 학과 │
├────────┼────────┼──────────┼────────┼─────────┤
│ 1001 │ 김철수 │ DB │ 이교수 │ 컴공 │
│ 1001 │ 김철수 │ 알고리즘 │ 박교수 │ 컴공 │
│ 1002 │ 이영희 │ DB │ 이교수 │ 전자 │
│ 1003 │ 박민수 │ OS │ 최교수 │ 컴공 │
└────────┴────────┴──────────┴────────┴─────────┘삽입 이상 (Insertion Anomaly)
불필요한 데이터 없이는 새로운 데이터를 삽입할 수 없는 문제입니다.
상황: 새 과목 "네트워크"를 개설하고 교수를 배정하고 싶다.
문제: 이 테이블의 기본키는 {학번, 과목명}이다.
아직 수강 신청한 학생이 없으면 학번이 NULL이 된다.
→ 기본키에 NULL을 넣을 수 없으므로 삽입 불가!
해결: 수강 신청 학생이 생길 때까지 과목 정보를 등록할 수 없다.
또는 가짜 학번을 넣어야 하는데, 이는 데이터 오염이다.삭제 이상 (Deletion Anomaly)
원하지 않는 데이터까지 함께 삭제되는 문제입니다.
상황: 1003 박민수가 휴학하여 수강 기록을 삭제한다.
문제: 박민수 행을 삭제하면...
OS 과목 정보(교수: 최교수)도 함께 사라진다!
→ 박민수만 삭제하려 했는데 과목 정보까지 소실
OS를 수강하는 학생이 박민수 한 명뿐이었기 때문이다.
만약 다른 학생도 OS를 수강 중이었다면 문제 없었을 것이다.갱신 이상 (Update Anomaly)
일부 행만 수정하면 데이터 불일치가 발생하는 문제입니다.
상황: "이교수"가 "이진수"로 개명했다.
문제: DB 과목을 가르치는 행이 2개(1001, 1002)다.
1001 행만 수정하고 1002 행은 수정하지 않으면...
┌────────┬────────┬──────────┬────────┬─────────┐
│ 1001 │ 김철수 │ DB │ 이진수 │ 컴공 │ ← 수정됨
│ 1002 │ 이영희 │ DB │ 이교수 │ 전자 │ ← 수정 안 됨!
└────────┴────────┴──────────┴────────┴─────────┘
같은 교수인데 이름이 다르게 저장됨 = 데이터 불일치이상 현상 요약
| 이상 현상 | 원인 | 결과 |
|---|---|---|
| 삽입 이상 | 불필요한 정보 없이 삽입 불가 | 데이터 등록 제한 |
| 삭제 이상 | 관련 없는 정보까지 삭제 | 정보 소실 |
| 갱신 이상 | 중복 데이터 일부만 수정 | 데이터 불일치 |
이상 현상의 근본 원인은 하나의 테이블에 여러 주제의 데이터가 섞여 있기 때문입니다. 위 테이블에서 학생 정보, 과목 정보, 수강 관계라는 세 가지 주제가 한 테이블에 뒤섞여 있습니다.
이상 현상의 근본 원인: 데이터 중복
이상 현상은 결국 데이터 중복에서 비롯됩니다. 같은 사실이 여러 행에 반복 저장되면, 한 곳을 수정할 때 다른 곳과 불일치가 생기고, 삭제할 때 의도치 않은 정보가 사라지며, 삽입할 때 불필요한 정보를 함께 넣어야 합니다.
데이터 중복 → 이상 현상 → 데이터 무결성 위반
중복이 발생하는 이유
1. 하나의 테이블에 여러 엔티티를 합침
2. 종속 관계가 다른 속성들을 한 곳에 저장
3. 반복 그룹(다중값 속성)을 그대로 저장
중복을 제거하는 방법
→ 정규화 (테이블 분해)함수 종속
함수 종속(Functional Dependency, FD)은 정규화의 이론적 기반입니다. 어떤 속성이 어떤 속성을 결정하는지를 나타내는 관계입니다.
속성 X의 값이 결정되면 속성 Y의 값이 유일하게 결정될 때, Y는 X에 함수적으로 종속된다고 하며 X → Y로 표기합니다. X를 결정자(Determinant), Y를 종속자(Dependent)라 합니다. 여기서 X와 Y는 단일 속성일 수도, 복합 속성일 수도 있습니다.
학번 → 이름 (학번이 정해지면 이름이 결정됨)
학번 → 학과 (학번이 정해지면 학과가 결정됨)
과목명 → 교수 (과목이 정해지면 교수가 결정됨)
{학번, 과목명} → 성적 (학번과 과목 조합이 성적을 결정)함수 종속과 수학 함수의 관계
수학: y = f(x) x가 정해지면 y가 하나로 결정됨
DB: 학번 → 이름 학번이 정해지면 이름이 하나로 결정됨
다만 역은 성립하지 않을 수 있음:
학번 → 이름 (O) 학번 1001 → 김철수
이름 → 학번 (X) 김철수가 여러 명일 수 있음함수 종속을 찾는 방법
함수 종속은 현재 데이터의 상태가 아니라, 비즈니스 규칙을 기반으로 판단해야 합니다. 현재 인스턴스에서 우연히 1:1 매핑이 되더라도, 논리적으로 항상 성립하지 않으면 함수 종속이 아닙니다.
1. 비즈니스 규칙을 파악한다
"한 과목은 한 교수만 담당한다" → 과목 → 교수
"한 학생은 하나의 학과에 소속된다" → 학번 → 학과
2. 현재 데이터로 판단하지 않는다!
현재 데이터: 김 → 컴공, 이 → 전자 (우연히 1:1)
하지만 김씨가 전자공학과일 수도 있으므로 이름 → 학과는 FD가 아님
3. 모든 가능한 인스턴스에서 성립해야 FD이다
규칙 기반으로 판단해야 하며, 특정 시점의 데이터만 보면 안 됨함수 종속의 유형
| 유형 | 정의 | 예시 | 정규화 연관 |
|---|---|---|---|
| 완전 함수 종속 | 기본키 전체에 종속 | {학번, 과목명} → 성적 | 정상 |
| 부분 함수 종속 | 기본키의 일부에만 종속 | {학번, 과목명} 중 학번 → 이름 | 2NF 위반 |
| 이행 함수 종속 | A→B, B→C이면 A→C | 학번→학과, 학과→학과장 | 3NF 위반 |
완전 함수 종속 (Full FD)
기본키: {학번, 과목명}
성적은 학번만으로는 결정되지 않고, 과목명만으로도 결정되지 않는다.
반드시 {학번, 과목명} 전체가 있어야 성적이 결정된다.
→ 성적은 {학번, 과목명}에 완전 함수 종속
→ 이 관계는 정상이며, 분해할 필요 없음부분 함수 종속 (Partial FD)
기본키: {학번, 과목명}
이름은 학번만으로 결정된다. 과목명은 필요 없다.
{학번, 과목명} → 이름 (기본키 전체가 결정)
학번 → 이름 (기본키의 일부만으로도 결정!)
→ 이름은 {학번, 과목명}에 부분 함수 종속
→ 2NF 위반: 학생 테이블로 분리해야 함이행 함수 종속 (Transitive FD)
학번 → 학과 (학번이 학과를 결정)
학과 → 학과장 (학과가 학과장을 결정)
따라서: 학번 → 학과 → 학과장 (이행적으로 종속)
문제: 학과장이 바뀌면 해당 학과 소속 모든 학생 행을 수정해야 함
→ 3NF 위반: 학과 테이블을 별도로 분리해야 함종속 관계 다이어그램
{학번, 과목명}
│
├──완전──→ 성적 ✓ 정상 (전체 키에 종속)
│
├──부분──→ 이름 ✗ 2NF 위반 (학번에만 종속)
├──부분──→ 학과 ✗ 2NF 위반
│
└──이행──→ 학과장 ✗ 3NF 위반 (학번→학과→학과장)
분해 방향:
수강(학번, 과목명, 성적) ← 완전 함수 종속만 남음
학생(학번, 이름, 학과코드) ← 부분 함수 종속 제거
학과(학과코드, 학과명, 학과장) ← 이행 함수 종속 제거부분 함수 종속과 이행 함수 종속을 제거하는 것이 정규화의 핵심입니다. 정규화의 각 단계는 바로 이 종속 유형들을 하나씩 제거하는 과정에 해당합니다.
함수 종속의 추론 규칙 (Armstrong's Axioms)
주어진 함수 종속 집합으로부터 새로운 함수 종속을 논리적으로 유도할 수 있습니다. 암스트롱의 공리는 이 추론의 기반이 되며, 건전(sound)하고 완전(complete)함이 증명되어 있습니다.
세 가지 기본 규칙
1. 반사 규칙: Y ⊆ X이면 X → Y
{학번, 이름} → 학번 (자명한 종속)
2. 확대 규칙: X → Y이면 XZ → YZ
학번 → 이름이면 {학번, 과목명} → {이름, 과목명}
3. 이행 규칙: X → Y이고 Y → Z이면 X → Z
학번 → 학과, 학과 → 학과장이면 학번 → 학과장
파생 규칙
4. 합집합 규칙: X → Y이고 X → Z이면 X → YZ
5. 분해 규칙: X → YZ이면 X → Y이고 X → Z
6. 가이행 규칙: X → Y이고 WY → Z이면 WX → Z정규화란
정규화(Normalization)는 릴레이션을 분해하여 이상 현상을 제거하는 과정입니다. 각 단계를 정규형(Normal Form)이라 하며, 상위 정규형은 하위 정규형의 조건을 모두 만족합니다. 즉, 3NF인 릴레이션은 반드시 2NF이고, 2NF인 릴레이션은 반드시 1NF입니다.
비정규형
│
├─ 원자값이 아닌 속성 제거 ──→ 1NF (제1정규형)
│
├─ 부분 함수 종속 제거 ─────→ 2NF (제2정규형)
│
├─ 이행 함수 종속 제거 ─────→ 3NF (제3정규형)
│
├─ 결정자가 후보키 ─────────→ BCNF
│
├─ 다치 종속 제거 ──────────→ 4NF
│
└─ 조인 종속 제거 ──────────→ 5NF정규화의 원칙
1. 정보 무손실 분해 (Lossless Decomposition)
분해한 테이블을 다시 조인하면 원래 데이터를 복원할 수 있어야 함
→ 의미 없는 행(스퓨리어스 튜플)이 생기면 안 됨
2. 함수 종속 보존 (Dependency Preservation)
분해 후에도 원래의 함수 종속이 유지되어야 함
→ 무결성 검증을 위해 조인이 필요하면 실용적이지 않음
3. 최소 중복 (Minimum Redundancy)
동일한 정보가 여러 곳에 저장되지 않도록 함
→ 이상 현상의 근본 원인 제거정규화와 반정규화
정규화
목적: 이상 현상 제거, 데이터 무결성 확보
방법: 테이블 분해
결과: 테이블 수 증가, 조인 증가
장점: 데이터 정합성 보장, 수정/삭제 안전
단점: 조회 시 조인으로 인한 성능 저하 가능
반정규화 (Denormalization)
목적: 조회 성능 개선
방법: 정규화된 테이블을 다시 합치거나 중복 허용
결과: 테이블 수 감소, 조인 감소
장점: 조회 속도 향상
단점: 데이터 중복으로 이상 현상 재발 가능
원칙: 먼저 정규화를 충분히 수행한 후,
성능이 문제될 때 필요한 부분만 반정규화
"정규화가 기본, 반정규화는 예외"정규화가 실무에서 왜 중요한가
1. 데이터 정합성 위반
고객 주소가 10곳에 중복 저장 → 한 곳만 수정하면 9곳은 구 주소
→ 어느 주소가 맞는지 확인 불가
2. 저장 공간 낭비
100만 건의 주문 테이블에 고객 정보(이름, 주소, 전화번호)가 반복
→ 고객 테이블 분리 시 중복 제거
3. 유지보수 어려움
컬럼 추가/수정 시 영향 범위가 불확실
→ 정규화된 구조는 변경 영향이 명확
4. 쿼리 복잡성
의외로, 정규화된 구조가 쿼리 작성이 더 쉬움
각 테이블의 의미가 명확하기 때문┌────────┬──────────────────────────────────────┐
│ 정규형 │ 실무 적용 │
├────────┼──────────────────────────────────────┤
│ 1NF │ 거의 모든 RDBMS가 강제 │
│ 2NF │ 대부분의 시스템에서 적용 │
│ 3NF │ 일반적인 운영 시스템의 표준 수준 │
│ BCNF │ 정밀한 설계가 필요한 경우 │
│ 4NF+ │ 학술적 의미가 강함, 실무에서는 드뭄 │
└────────┴──────────────────────────────────────┘
모든 단계에서 정보 무손실 분해 원칙은 반드시 지켜져야 합니다.
실무 기준: 3NF까지 적용하면 대부분의 이상 현상을 방지할 수 있습니다.정규화 사고 과정: 실전 예제
비정규화된 테이블을 보고 이상 현상과 함수 종속을 식별하는 연습을 해봅시다. 정규화 문제를 풀 때는 항상 함수 종속을 먼저 찾고, 이상 현상을 확인한 뒤, 분해 방향을 결정하는 순서를 따릅니다.
┌────────┬────────┬────────┬──────┬────────┬──────┬──────────┐
│주문번호│고객번호│고객이름│전화번호│상품코드│ 수량 │ 단가 │
├────────┼────────┼────────┼──────┼────────┼──────┼──────────┤
│ O001 │ C01 │ 김철수 │ 010 │ P001 │ 2 │ 10000 │
│ O001 │ C01 │ 김철수 │ 010 │ P002 │ 1 │ 20000 │
│ O002 │ C02 │ 이영희 │ 011 │ P001 │ 3 │ 10000 │
│ O003 │ C01 │ 김철수 │ 010 │ P003 │ 1 │ 30000 │
└────────┴────────┴────────┴──────┴────────┴──────┴──────────┘
기본키: {주문번호, 상품코드}1단계: 함수 종속 찾기
주문번호 → 고객번호 (주문은 한 고객이 함)
고객번호 → 고객이름, 전화번호 (고객 정보)
상품코드 → 단가 (상품 가격은 상품이 결정)
{주문번호, 상품코드} → 수량 (얼마나 주문했는지)
2단계: 이상 현상 확인
부분 FD: 주문번호 → 고객번호 (키의 일부에 종속) → 2NF 위반
부분 FD: 상품코드 → 단가 (키의 일부에 종속) → 2NF 위반
이행 FD: 주문번호 → 고객번호 → 고객이름 → 3NF 위반
3단계: 분해
주문상세(주문번호, 상품코드, 수량) ← 완전 FD만 유지
주문(주문번호, 고객번호) ← 부분 FD 제거
고객(고객번호, 고객이름, 전화번호) ← 이행 FD 제거
상품(상품코드, 단가) ← 부분 FD 제거분해 후 검증
정규화를 통한 테이블 분해가 올바르게 이루어졌는지 검증하는 것은 매우 중요합니다. 분해된 테이블을 다시 자연 조인(Natural Join)했을 때 원래 테이블과 동일한 결과가 나와야 하며, 원래 없던 행이 생겨서도 안 됩니다.
SELECT od.주문번호, o.고객번호, c.고객이름, c.전화번호,
od.상품코드, od.수량, p.단가
FROM 주문상세 od
JOIN 주문 o ON od.주문번호 = o.주문번호
JOIN 고객 c ON o.고객번호 = c.고객번호
JOIN 상품 p ON od.상품코드 = p.상품코드;
→ 원본 테이블과 동일한 결과 (정보 무손실 분해 ✓)
→ 스퓨리어스 튜플(가짜 행) 없음 ✓분해 후 이상 현상 확인
정규화의 목적이 이상 현상 제거이므로, 분해 후 세 가지 이상 현상이 모두 사라졌는지 하나씩 확인해야 합니다.
삽입 이상
새 고객 등록 → 고객 테이블에 INSERT (주문 없어도 가능 ✓)
새 상품 등록 → 상품 테이블에 INSERT (주문 없어도 가능 ✓)
삭제 이상
주문 삭제 → 고객 정보는 고객 테이블에 남아 있음 ✓
상품 정보도 상품 테이블에 남아 있음 ✓
갱신 이상
고객 전화번호 변경 → 고객 테이블 한 행만 수정 ✓
상품 단가 변경 → 상품 테이블 한 행만 수정 ✓클로저와 후보키 찾기
함수 종속 집합이 주어지면 속성 클로저(Attribute Closure)를 계산하여 후보키를 찾을 수 있습니다. 클로저란 주어진 속성 집합으로부터 함수 종속을 반복 적용하여 결정할 수 있는 모든 속성의 집합입니다.
주어진 FD 집합: {A→B, B→C, A→D, D→E}
A의 클로저 A⁺ 계산
시작: A⁺ = {A}
A→B 적용: A⁺ = {A, B}
B→C 적용: A⁺ = {A, B, C}
A→D 적용: A⁺ = {A, B, C, D}
D→E 적용: A⁺ = {A, B, C, D, E}
A⁺ = 전체 속성 → A는 슈퍼키
A의 부분집합에서 같은 결과 없음 → A는 후보키1. 어떤 FD의 종속자에도 나타나지 않는 속성 찾기
→ 반드시 후보키에 포함됨
2. 그 속성의 클로저를 계산
→ 전체 속성이 포함되면 후보키
3. 포함되지 않으면 다른 속성을 추가하며 확인
예시: R(A, B, C, D, E), FD: {AB→C, C→D, D→E}
종속자: C, D, E
결정자에만 등장: A, B → 후보키에 반드시 포함
{A, B}⁺ = {A, B, C, D, E} → 후보키 = {A, B}핵심 정리
┌────────────────────────────────────────────────────────┐
│ 이상 현상 (Anomaly) │
│ 삽입 이상: 불필요한 데이터 없이 삽입 불가 │
│ 삭제 이상: 원하지 않는 정보까지 삭제 │
│ 갱신 이상: 일부만 수정 시 데이터 불일치 │
│ 원인: 하나의 테이블에 여러 주제가 섞여 있음 │
├────────────────────────────────────────────────────────┤
│ 함수 종속 (Functional Dependency) │
│ X → Y: X가 결정되면 Y가 유일하게 결정됨 │
│ 완전 FD: 키 전체에 종속 (정상) │
│ 부분 FD: 키의 일부에 종속 (2NF 위반) │
│ 이행 FD: A→B→C (3NF 위반) │
├────────────────────────────────────────────────────────┤
│ 정규화 │
│ 목적: 이상 현상 제거, 데이터 무결성 확보 │
│ 원칙: 정보 무손실 분해, 함수 종속 보존 │
│ 실무 수준: 3NF까지가 일반적 표준 │
│ 정규화 후 필요 시 반정규화 (성능 최적화) │
└────────────────────────────────────────────────────────┘□ 테이블이 하나의 주제(엔티티)만 다루고 있는가?
□ 모든 비키 속성이 기본키 전체에 완전 함수 종속인가?
□ 비키 속성 간 종속 관계(이행 FD)가 없는가?
□ 모든 결정자가 후보키인가?
□ 분해 후 정보 무손실 조인이 가능한가?
□ 분해 후 원래의 함수 종속이 보존되는가?실무에서는 보통 3NF 또는 BCNF까지 적용합니다. 4NF와 5NF는 이론적으로 중요하지만, 실무에서 마주치는 경우는 드뭅니다. 다음 절에서 각 정규형을 예제와 함께 실습합니다.