테이블을 어떻게 나눠야 "잘 설계한 DB"라고 할 수 있을까?

데이터베이스 설계에서 가장 기본이 되는 개념이 정규화입니다. 공부하다 보니 정규화 자체는 어렵지 않은데, "언제 정규화하고 언제 안 하는가" 의 판단 기준이 실제로 중요한 포인트였습니다.

정규화란

데이터 중복을 최소화하고, 이상 현상(Anomaly)을 방지하기 위해 테이블을 분리하는 과정

이상 현상이란:

  • **삽입 이상 **: 불필요한 데이터를 함께 넣어야 삽입 가능
  • ** 갱신 이상 **: 중복 데이터 중 일부만 수정 → 데이터 불일치
  • ** 삭제 이상 **: 데이터 삭제 시 의도치 않은 다른 정보까지 사라짐

제1정규형 (1NF) — 원자성

모든 컬럼의 값이 ** 원자값(Atomic Value)**이어야 한다.

하나의 셀에 여러 값이 들어가면 안 됩니다.

PLAINTEXT
❌ 1NF 위반
| 학생ID | 이름   | 수강과목          |
|--------|--------|-------------------|
| 1      | 김철수 | 수학, 영어, 과학   |

✅ 1NF 만족
| 학생ID | 이름   | 수강과목 |
|--------|--------|----------|
| 1      | 김철수 | 수학     |
| 1      | 김철수 | 영어     |
| 1      | 김철수 | 과학     |

실무에서 "콤마로 구분된 문자열"을 컬럼에 넣는 건 전형적인 1NF 위반입니다. 검색도 어렵고 수정도 번거롭습니다.

제2정규형 (2NF) — 부분 함수 종속 제거

1NF를 만족하면서, ** 기본키의 일부에만 종속되는 컬럼 **을 분리한다.

복합 기본키가 있을 때 발생하는 문제입니다.

PLAINTEXT
❌ 2NF 위반 — 기본키: (학생ID, 과목ID)
| 학생ID | 과목ID | 학생이름 | 성적 |
|--------|--------|----------|------|
| 1      | 101    | 김철수   | A    |

→ "학생이름"은 학생ID만으로 결정됨 (과목ID와 무관)
→ 부분 함수 종속 발생

✅ 2NF 만족 — 테이블 분리
[학생] 학생ID → 학생이름
[수강] (학생ID, 과목ID) → 성적

기본키가 단일 컬럼이면 2NF는 자동으로 만족됩니다. 복합키일 때만 신경 쓰면 됩니다.

제3정규형 (3NF) — 이행 함수 종속 제거

2NF를 만족하면서, ** 기본키가 아닌 컬럼이 다른 비키 컬럼을 결정하는 관계 **를 제거한다.

PLAINTEXT
❌ 3NF 위반
| 학생ID | 학과   | 학과장   |
|--------|--------|----------|
| 1      | 컴공   | 이교수   |
| 2      | 컴공   | 이교수   |

→ 학생ID → 학과 → 학과장 (이행 함수 종속)
→ 학과장이 바뀌면 모든 행을 수정해야 함 (갱신 이상)

✅ 3NF 만족 — 테이블 분리
[학생] 학생ID → 학과
[학과] 학과 → 학과장

BCNF (Boyce-Codd Normal Form)

모든 결정자가 후보키여야 한다.

3NF보다 조금 더 엄격한 형태입니다. 실무에서 3NF와 BCNF의 차이가 문제되는 경우는 드물지만, 알아두면 좋습니다.

PLAINTEXT
❌ BCNF 위반 예시
| 학생 | 과목   | 교수   |
|------|--------|--------|
| A    | DB     | 이교수 |
| B    | DB     | 김교수 |

→ 교수 → 과목 (교수가 결정자인데 후보키가 아님)

왜 실무에서 3NF까지만 하는가

4NF, 5NF도 존재하지만 실무에서는 거의 3NF(또는 BCNF)까지만 적용합니다.

  • 4NF 이상은 다치 종속, 조인 종속 등 복잡한 케이스를 다룸
  • 과도한 정규화 → 테이블이 너무 많아져 JOIN 비용 증가
  • 대부분의 이상 현상은 3NF로 해결됨

반정규화 (Denormalization)

** 의도적으로 중복을 허용 **하여 읽기 성능을 높이는 기법

정규화의 반대가 아니라, ** 정규화를 먼저 한 뒤** 성능상 필요한 부분만 선택적으로 되돌리는 것입니다.

주요 반정규화 기법

1. 중복 컬럼 추가

SQL
-- 정규화된 상태: 주문 조회 시 매번 고객 테이블 JOIN
SELECT o.*, c.name FROM orders o JOIN customers c ON o.customer_id = c.id;

-- 반정규화: 자주 조회하는 고객명을 주문 테이블에 추가
ALTER TABLE orders ADD COLUMN customer_name VARCHAR(100);

2. 계산 컬럼 추가

SQL
-- 매번 SUM 집계하는 대신
ALTER TABLE orders ADD COLUMN total_amount DECIMAL(10,2);
-- 주문 항목 변경 시 트리거나 애플리케이션 로직으로 갱신

3. 테이블 병합

SQL
-- 1:1 관계인 두 테이블을 하나로 합침
-- user + user_profile → user (프로필 컬럼 포함)

4. 테이블 분할 (파티셔닝)

  • 수직 분할: 자주 쓰는 컬럼과 안 쓰는 컬럼 분리
  • 수평 분할: 데이터를 날짜나 범위로 분리

OLTP vs OLAP에서의 차이

구분OLTPOLAP
목적트랜잭션 처리 (주문, 결제)분석, 리포팅
작업 비율쓰기 > 읽기읽기 >> 쓰기
정규화3NF 정규화 유지** 반정규화** (스타/스노우플레이크 스키마)
이유데이터 무결성, 이상 현상 방지JOIN 비용 절감, 조회 성능

업계 표준은 "OLTP는 정규화, OLAP는 반정규화" 입니다.

실전 판단 기준

정규화와 반정규화 사이에서 어떻게 결정할까요?

정규화를 유지하는 경우

  • 쓰기가 빈번한 트랜잭션 시스템
  • 데이터 무결성이 최우선 (금융, 의료)
  • ACID 보장이 필수
  • 정규화만으로 쓰기 성능이 약 20% 향상될 수 있음

반정규화를 고려하는 경우

  • 읽기 위주 시스템 (대시보드, 통계)
  • 복잡한 JOIN이 성능 병목
  • 응답 속도가 최우선 (사용자 대면 API)
  • 반정규화로 읽기 성능이 최대 100% 향상될 수 있음

자주 하는 실수

  • **처음부터 반정규화로 설계 **: 나중에 요구사항이 바뀌면 데이터 정합성 문제 폭발
  • ** 모든 테이블을 과도하게 정규화 **: JOIN 10개짜리 쿼리가 나오면 유지보수가 어려움
  • ** 반정규화 후 동기화 로직을 빠뜨림 **: 중복 데이터가 서로 달라지는 문제

정리

  • 정규화는 데이터 중복을 제거하고 이상 현상을 방지하는 과정
  • 1NF(원자성) → 2NF(부분 함수 종속 제거) → 3NF(이행 함수 종속 제거)
  • 실무에서는 3NF까지면 대부분 충분
  • 반정규화는 정규화를 먼저 한 뒤, 읽기 성능이 필요한 곳에 선택적으로 적용
  • OLTP = 정규화, OLAP = 반정규화가 업계 표준

정규화가 먼저이고, 반정규화는 "측정된 성능 문제"에 대한 해결책입니다. 감으로 반정규화하면 나중에 데이터 정합성 지옥을 경험하게 됩니다.

댓글 로딩 중...