데이터베이스 정규화 - 이론부터 실무까지
정규화(Normalization)는 관계형 데이터베이스 설계에서 데이터 중복을 최소화하고 데이터 무결성을 보장하기 위한 과정이다.
이상 현상 (Anomaly)
정규화되지 않은 테이블에서 발생하는 데이터 불일치 문제
예시 테이블 - 수강 정보
| 학번 | 학생명 | 학과 | 과목코드 | 과목명 | 교수 |
|---|---|---|---|---|---|
| 1001 | 김철수 | 컴퓨터공학 | CS101 | 자료구조 | 이교수 |
| 1001 | 김철수 | 컴퓨터공학 | CS102 | 알고리즘 | 박교수 |
| 1002 | 이영희 | 전자공학 | CS101 | 자료구조 | 이교수 |
| 1003 | 박민수 | 컴퓨터공학 | CS103 | 데이터베이스 | 최교수 |
1. 삽입 이상 (Insertion Anomaly)
새로운 과목 “운영체제(CS104)”를 추가하고 싶지만, 아직 수강생이 없어서 학번, 학생명이 NULL이 되어야 함.
-- 학번이 PK의 일부라면 삽입 불가능!
INSERT INTO 수강정보 (학번, 학생명, 학과, 과목코드, 과목명, 교수)
VALUES (NULL, NULL, NULL, 'CS104', '운영체제', '정교수');
2. 삭제 이상 (Deletion Anomaly)
박민수(1003)가 수강 취소하면 “데이터베이스” 과목 정보도 함께 삭제됨.
-- 박민수 삭제 시 CS103 과목 정보도 사라짐!
DELETE FROM 수강정보 WHERE 학번 = 1003;
3. 갱신 이상 (Update Anomaly)
“자료구조” 담당 교수가 변경되면 여러 행을 수정해야 함. 일부만 수정하면 데이터 불일치 발생.
-- 1001번 행만 수정하면 1002번 행과 불일치!
UPDATE 수강정보 SET 교수 = '김교수'
WHERE 학번 = 1001 AND 과목코드 = 'CS101';
-- 결과: 같은 과목인데 교수가 다름 (데이터 모순)
함수적 종속 (Functional Dependency)
정규화를 이해하기 위한 핵심 개념
정의
X → Y : X 값이 Y 값을 유일하게 결정한다.
학번 → 학생명 (학번이 결정되면 학생명이 결정됨)
과목코드 → 과목명 (과목코드가 결정되면 과목명이 결정됨)
과목코드 → 교수 (과목코드가 결정되면 담당교수가 결정됨)
종류
| 종류 | 설명 | 예시 |
|---|---|---|
| 완전 함수 종속 | 기본키 전체에 종속 | {학번, 과목코드} → 성적 |
| 부분 함수 종속 | 기본키 일부에 종속 | {학번, 과목코드} → 학생명 (학번만으로 결정) |
| 이행적 함수 종속 | A→B, B→C이면 A→C | 학번→학과, 학과→학과장 ⇒ 학번→학과장 |
정규화 단계
비정규형 → 1NF → 2NF → 3NF → BCNF → 4NF → 5NF
원자값 부분종속 이행종속 결정자 다치종속 조인종속
제거 제거 검증 제거 제거
실무에서는 보통 3NF 또는 BCNF까지 적용한다.
제1정규형 (1NF)
모든 속성의 도메인이 원자값(Atomic Value)으로만 구성되어야 한다.
위반 사례
| 학번 | 학생명 | 수강과목 |
|---|---|---|
| 1001 | 김철수 | 자료구조, 알고리즘, DB |
| 1002 | 이영희 | 자료구조, 네트워크 |
문제점: 수강과목이 다중 값(Multi-valued)
1NF 적용 후
| 학번 | 학생명 | 수강과목 |
|---|---|---|
| 1001 | 김철수 | 자료구조 |
| 1001 | 김철수 | 알고리즘 |
| 1001 | 김철수 | DB |
| 1002 | 이영희 | 자료구조 |
| 1002 | 이영희 | 네트워크 |
-- 1NF 위반 테이블 (안티패턴)
CREATE TABLE 수강_bad (
학번 INT,
학생명 VARCHAR(50),
수강과목 VARCHAR(200) -- 콤마로 구분된 값 저장
);
-- 1NF 준수 테이블
CREATE TABLE 수강 (
학번 INT,
학생명 VARCHAR(50),
수강과목 VARCHAR(50),
PRIMARY KEY (학번, 수강과목)
);
제2정규형 (2NF)
1NF를 만족하고, 부분 함수 종속을 제거해야 한다. 기본키가 복합키일 때, 기본키의 일부에만 종속되는 속성이 없어야 한다.
위반 사례
기본키: {학번, 과목코드}
| 학번 | 과목코드 | 학생명 | 학과 | 과목명 | 성적 |
|---|---|---|---|---|---|
| 1001 | CS101 | 김철수 | 컴공 | 자료구조 | A |
| 1001 | CS102 | 김철수 | 컴공 | 알고리즘 | B |
| 1002 | CS101 | 이영희 | 전자 | 자료구조 | A |
함수 종속 분석:
{학번, 과목코드} → 성적 (완전 함수 종속 ✓)
{학번} → 학생명, 학과 (부분 함수 종속 ✗)
{과목코드} → 과목명 (부분 함수 종속 ✗)
2NF 적용 후
학생 테이블
| 학번 | 학생명 | 학과 |
|---|---|---|
| 1001 | 김철수 | 컴공 |
| 1002 | 이영희 | 전자 |
과목 테이블
| 과목코드 | 과목명 |
|---|---|
| CS101 | 자료구조 |
| CS102 | 알고리즘 |
수강 테이블
| 학번 | 과목코드 | 성적 |
|---|---|---|
| 1001 | CS101 | A |
| 1001 | CS102 | B |
| 1002 | CS101 | A |
-- 2NF 적용
CREATE TABLE 학생 (
학번 INT PRIMARY KEY,
학생명 VARCHAR(50),
학과 VARCHAR(50)
);
CREATE TABLE 과목 (
과목코드 VARCHAR(10) PRIMARY KEY,
과목명 VARCHAR(100)
);
CREATE TABLE 수강 (
학번 INT,
과목코드 VARCHAR(10),
성적 CHAR(1),
PRIMARY KEY (학번, 과목코드),
FOREIGN KEY (학번) REFERENCES 학생(학번),
FOREIGN KEY (과목코드) REFERENCES 과목(과목코드)
);
제3정규형 (3NF)
2NF를 만족하고, 이행적 함수 종속을 제거해야 한다. 기본키가 아닌 속성이 다른 비키 속성에 종속되면 안 된다.
위반 사례
| 학번 | 학생명 | 학과코드 | 학과명 | 학과장 |
|---|---|---|---|---|
| 1001 | 김철수 | CS | 컴퓨터공학 | 이교수 |
| 1002 | 이영희 | EE | 전자공학 | 박교수 |
| 1003 | 박민수 | CS | 컴퓨터공학 | 이교수 |
함수 종속 분석:
학번 → 학과코드
학과코드 → 학과명, 학과장
------------------------------
학번 → 학과명, 학과장 (이행적 종속!)
문제점: 학과장이 바뀌면 여러 행 수정 필요
3NF 적용 후
학생 테이블
| 학번 | 학생명 | 학과코드 |
|---|---|---|
| 1001 | 김철수 | CS |
| 1002 | 이영희 | EE |
| 1003 | 박민수 | CS |
학과 테이블
| 학과코드 | 학과명 | 학과장 |
|---|---|---|
| CS | 컴퓨터공학 | 이교수 |
| EE | 전자공학 | 박교수 |
-- 3NF 적용
CREATE TABLE 학과 (
학과코드 VARCHAR(10) PRIMARY KEY,
학과명 VARCHAR(50),
학과장 VARCHAR(50)
);
CREATE TABLE 학생 (
학번 INT PRIMARY KEY,
학생명 VARCHAR(50),
학과코드 VARCHAR(10),
FOREIGN KEY (학과코드) REFERENCES 학과(학과코드)
);
BCNF (Boyce-Codd Normal Form)
3NF를 만족하고, 모든 결정자가 후보키여야 한다. 3NF보다 엄격한 조건
3NF는 만족하지만 BCNF 위반 사례
수강 테이블 (한 학생은 한 과목당 한 교수에게만 수강)
| 학번 | 과목 | 교수 |
|---|---|---|
| 1001 | DB | 김교수 |
| 1001 | 알고리즘 | 이교수 |
| 1002 | DB | 김교수 |
| 1003 | DB | 박교수 |
후보키: {학번, 과목} 또는 {학번, 교수}
함수 종속:
{학번, 과목} → 교수
{학번, 교수} → 과목
교수 → 과목 (결정자가 후보키가 아님! BCNF 위반)
문제점: 김교수가 담당 과목을 변경하면 여러 행 수정 필요
BCNF 적용 후
교수_과목 테이블
| 교수 | 과목 |
|---|---|
| 김교수 | DB |
| 이교수 | 알고리즘 |
| 박교수 | DB |
수강 테이블
| 학번 | 교수 |
|---|---|
| 1001 | 김교수 |
| 1001 | 이교수 |
| 1002 | 김교수 |
| 1003 | 박교수 |
-- BCNF 적용
CREATE TABLE 교수_과목 (
교수 VARCHAR(50) PRIMARY KEY,
과목 VARCHAR(50)
);
CREATE TABLE 수강 (
학번 INT,
교수 VARCHAR(50),
PRIMARY KEY (학번, 교수),
FOREIGN KEY (교수) REFERENCES 교수_과목(교수)
);
제4정규형 (4NF)
BCNF를 만족하고, 다치 종속(Multi-valued Dependency)을 제거해야 한다.
다치 종속이란?
A →→ B : A 값 하나에 B 값이 여러 개 대응되며, 다른 속성과 독립적
위반 사례
| 교수 | 과목 | 취미 |
|---|---|---|
| 김교수 | DB | 골프 |
| 김교수 | DB | 독서 |
| 김교수 | 알고리즘 | 골프 |
| 김교수 | 알고리즘 | 독서 |
다치 종속:
교수 →→ 과목 (김교수는 DB, 알고리즘 담당)
교수 →→ 취미 (김교수는 골프, 독서가 취미)
과목과 취미는 서로 독립적인데 모든 조합이 저장됨 (카티션 곱)
4NF 적용 후
교수_과목 테이블
| 교수 | 과목 |
|---|---|
| 김교수 | DB |
| 김교수 | 알고리즘 |
교수_취미 테이블
| 교수 | 취미 |
|---|---|
| 김교수 | 골프 |
| 김교수 | 독서 |
제5정규형 (5NF)
4NF를 만족하고, 조인 종속(Join Dependency)을 제거해야 한다. 더 이상 무손실 분해가 불가능한 상태
실무에서는 거의 사용하지 않음. 이론적 완전성을 위한 정규형.
정규화 정리
| 정규형 | 조건 | 제거 대상 |
|---|---|---|
| 1NF | 원자값만 허용 | 반복 그룹, 다중 값 |
| 2NF | 1NF + 완전 함수 종속 | 부분 함수 종속 |
| 3NF | 2NF + 비이행적 종속 | 이행적 함수 종속 |
| BCNF | 모든 결정자가 후보키 | 후보키 아닌 결정자 |
| 4NF | BCNF + 다치 종속 제거 | 다치 종속 |
| 5NF | 4NF + 조인 종속 제거 | 조인 종속 |
역정규화 (Denormalization)
정규화의 반대 과정. 성능 향상을 위해 의도적으로 중복을 허용하는 것.
역정규화가 필요한 경우
- 조인이 너무 많은 경우
-- 정규화된 테이블: 6개 테이블 조인 SELECT o.order_id, c.name, p.product_name, ... FROM orders o JOIN customers c ON ... JOIN order_items oi ON ... JOIN products p ON ... JOIN categories cat ON ... JOIN shipping s ON ... - 집계 연산이 빈번한 경우
-- 매번 COUNT 계산 SELECT user_id, COUNT(*) as post_count FROM posts GROUP BY user_id; -- 역정규화: users 테이블에 post_count 컬럼 추가 - 읽기 성능이 중요한 경우 (OLAP, 리포팅)
역정규화 기법
| 기법 | 설명 | 예시 |
|---|---|---|
| 컬럼 중복 | 자주 조회하는 컬럼 복사 | 주문 테이블에 고객명 추가 |
| 파생 컬럼 | 계산 결과 저장 | 주문 테이블에 총액 저장 |
| 테이블 병합 | 1:1 관계 테이블 합침 | 사용자+프로필 병합 |
| 요약 테이블 | 집계 데이터 별도 저장 | 일별 매출 요약 테이블 |
역정규화 시 주의사항
// 중복 데이터 동기화 필요
@Transactional
public void updateCustomerName(Long customerId, String newName) {
// 1. 고객 테이블 업데이트
customerRepository.updateName(customerId, newName);
// 2. 주문 테이블의 중복 데이터도 업데이트 (동기화)
orderRepository.updateCustomerName(customerId, newName);
}
실무 가이드
1. 보통 3NF 또는 BCNF까지 적용
OLTP 시스템 → 3NF/BCNF (데이터 무결성 중요)
OLAP 시스템 → 역정규화 적용 (조회 성능 중요)
2. 정규화 vs 역정규화 판단 기준
| 기준 | 정규화 | 역정규화 |
|---|---|---|
| 데이터 변경 빈도 | 높음 | 낮음 |
| 조회 성능 요구 | 보통 | 높음 |
| 데이터 무결성 | 중요 | 타협 가능 |
| 저장 공간 | 절약 | 여유 |
3. 단계적 접근
1단계: 완전히 정규화된 설계로 시작
2단계: 성능 테스트 수행
3단계: 병목 지점 발견 시 선택적 역정규화
4단계: 트리거/애플리케이션으로 데이터 동기화 구현
정규화는 데이터 무결성을 위한 것이고, 역정규화는 성능 최적화를 위한 것이다. 둘 사이의 균형을 찾는 것이 좋은 데이터베이스 설계다.
댓글