정규화 (Normalization)
정규화(Normalization): 데이터 중복을 줄이고 이상(Anomaly)을 방지하기 위해 테이블을 구조적으로 분리하는 설계 과정. 각 단계를 정규형(Normal Form, NF)이라 한다.
왜 쓰는가?
정규화 없이 설계하면 데이터를 삽입·수정·삭제할 때 이상(Anomaly)이 발생한다.
| 이상 종류 | 설명 | 예시 |
|---|---|---|
| 삽입 이상 | 일부 데이터가 없으면 행 자체를 삽입 불가 | 수강 신청 없으면 학생 정보를 저장할 수 없음 |
| 갱신 이상 | 중복 데이터 중 일부만 수정되어 불일치 | 교수 이름이 여러 행에 중복 저장 → 한 곳만 수정되면 불일치 |
| 삭제 이상 | 데이터 삭제 시 의도치 않은 다른 정보도 사라짐 | 수강 취소 시 교수 정보까지 삭제됨 |
함수 종속성 (Functional Dependency)
정규화의 핵심 개념. 컬럼 A의 값이 결정되면 B의 값도 결정될 때 "B는 A에 함수 종속"이라 한다.
| 종류 | 설명 |
|---|---|
| 완전 함수 종속 | 기본키 전체에 종속 (복합키일 때 일부가 아닌 전체에 종속) |
| 부분 함수 종속 | 복합키의 일부에만 종속 → 2NF 위반 |
| 이행 함수 종속 | A → B → C (A가 C를 간접 결정) → 3NF 위반 |
정규형 단계별 정리
1NF — 원자값
조건: 모든 컬럼 값이 원자값(Atomic Value)이어야 한다. 즉, 한 셀에 여러 값이 들어가면 안 된다.
Bad — 1NF 위반
| 학번 | 이름 | 수강과목 |
|---|---|---|
| 1001 | 김철수 | DB, Java, Spring |
→ 수강과목 열에 여러 값이 존재
Good — 1NF 만족
| 학번 | 이름 | 수강과목 |
|---|---|---|
| 1001 | 김철수 | DB |
| 1001 | 김철수 | Java |
| 1001 | 김철수 | Spring |
→ 각 행이 원자값
2NF — 부분 함수 종속 제거
조건: 1NF를 만족하면서, 기본키가 복합키일 때 비키 컬럼이 기본키 전체에 완전 함수 종속이어야 한다.
Bad — 2NF 위반
| 학번 | 과목코드 | 이름 | 과목명 |
|---|---|---|---|
| 1001 | CS101 | 김철수 | 데이터베이스 |
기본키: (학번, 과목코드)
- 이름 → 학번에만 종속 (부분 종속) ❌
- 과목명 → 과목코드에만 종속 (부분 종속) ❌
Good — 2NF 만족 (테이블 분리)
학생 테이블
| 학번 | 이름 |
|---|---|
| 1001 | 김철수 |
과목 테이블
| 과목코드 | 과목명 |
|---|---|
| CS101 | 데이터베이스 |
수강 테이블
| 학번 | 과목코드 |
|---|---|
| 1001 | CS101 |
3NF — 이행 함수 종속 제거
조건: 2NF를 만족하면서, 이행 함수 종속(A → B → C)이 없어야 한다. 비키 컬럼끼리 종속 관계가 없어야 한다.
Bad — 3NF 위반
| 사원번호 | 사원명 | 부서코드 | 부서명 |
|---|---|---|---|
| E001 | 김철수 | D10 | 개발팀 |
사원번호 → 부서코드 → 부서명 (이행 종속) ❌
Good — 3NF 만족 (테이블 분리)
사원 테이블
| 사원번호 | 사원명 | 부서코드 |
|---|---|---|
| E001 | 김철수 | D10 |
부서 테이블
| 부서코드 | 부서명 |
|---|---|
| D10 | 개발팀 |
BCNF — 결정자가 후보키
조건: 3NF를 만족하면서, 함수 종속 관계에서 모든 결정자가 후보키여야 한다. 3NF보다 엄격한 형태.
Bad — BCNF 위반
| 학번 | 과목 | 교수 |
|---|---|---|
| 1001 | DB | 박교수 |
| 1002 | DB | 박교수 |
(학번, 과목) → 교수✓ (후보키)교수 → 과목❌ (교수는 후보키가 아닌데 결정자)
Good — BCNF 만족
수강 테이블
| 학번 | 교수 |
|---|---|
| 1001 | 박교수 |
| 1002 | 박교수 |
교수 테이블
| 교수 | 과목 |
|---|---|
| 박교수 | DB |
정규형 요약 비교
| 정규형 | 조건 | 제거 대상 |
|---|---|---|
| 1NF | 원자값 | 반복 그룹, 다중 값 |
| 2NF | 1NF + 완전 함수 종속 | 부분 함수 종속 |
| 3NF | 2NF + 이행 종속 제거 | 이행 함수 종속 |
| BCNF | 3NF + 결정자 = 후보키 | 비후보키 결정자 |
실무에서는 보통 3NF 또는 BCNF까지 정규화하는 것이 일반적이다.
역정규화 (Denormalization)
역정규화: 정규화된 테이블을 의도적으로 합치거나 중복을 허용해 읽기 성능을 높이는 기법.
정규화를 하면 JOIN이 많아지고 대규모 조회 쿼리의 성능이 저하될 수 있다. 이때 역정규화로 트레이드오프를 선택한다.
-- 정규화: 주문 + 회원 JOIN 필요
SELECT o.order_id, m.name, m.email
FROM orders o
JOIN member m ON o.member_id = m.id;
-- 역정규화: orders 테이블에 member_name, member_email 컬럼 포함
-- → JOIN 없이 단일 테이블 조회 (읽기 성능 향상, 중복 데이터 증가)
SELECT order_id, member_name, member_email FROM orders;
| 항목 | 정규화 | 역정규화 |
|---|---|---|
| 데이터 중복 | 없음 | 있음 |
| 이상(Anomaly) | 없음 | 발생 가능 |
| 쓰기 성능 | 좋음 | 갱신 이상 위험 |
| 읽기 성능 | JOIN 비용 발생 | 빠름 |
| 적합 상황 | 정합성 중요 (금융, 결제) | 조회 빈번 (통계, 캐시 테이블) |
장점
- 데이터 중복 최소화 → 저장 공간 효율
- 갱신 이상 방지 → 데이터 정합성 유지
- 구조가 명확해 유지보수 용이
단점
- 테이블 수 증가 → JOIN이 많아져 복잡한 쿼리 필요
- 과도한 정규화는 읽기 성능 저하
- 설계 난이도 상승
주의할 점
정규화와 역정규화는 트레이드오프다. 무조건 높은 정규형이 좋은 것이 아니다. 서비스 특성(쓰기 빈도 vs 읽기 빈도, 정합성 요구 수준)에 맞게 선택해야 한다.
역정규화 후 갱신 이상: 중복 컬럼을 여러 테이블에 분산하면 한 곳을 수정할 때 다른 곳도 반드시 함께 수정해야 한다. 애플리케이션 레벨에서 동기화 로직을 관리해야 한다.
팁: 역정규화는 성능 문제가 실제로 측정된 뒤에 적용한다. 섣불리 역정규화하면 유지보수 비용이 급증한다.