데이터베이스 정규화 완벽 가이드 — 1NF부터 3NF까지
정규화(Normalization)는 테이블을 잘게 쪼개는 규칙처럼 보이지만, 실은 "같은 사실을 여러 곳에 중복해서 적어두지 않기 위한" 매우 실용적인 절차입니다. 이 글은 이상현상이 실제로 어떻게 생기는지 예제 테이블로 직접 보여주고, 1NF·2NF·3NF를 순서대로 적용하며 문제를 해결하는 과정을 따라갑니다.
왜 정규화하는가 — 이상현상 직접 보기
다음과 같이 주문 정보를 하나의 테이블에 전부 몰아넣었다고 해봅시다.
| 주문번호 | 고객명 | 고객이메일 | 상품명 | 단가 | 수량 |
|---|---|---|---|---|---|
| 1001 | 김민준 | minjun@ex.com | 키보드 | 45000 | 1 |
| 1001 | 김민준 | minjun@ex.com | 마우스 | 25000 | 2 |
| 1002 | 이서연 | seoyeon@ex.com | 모니터 | 210000 | 1 |
작동은 합니다. 하지만 이 구조는 세 가지 이상현상(anomaly)을 안고 있습니다.
- 갱신 이상 — 김민준 고객의 이메일이 바뀌면 1001번 주문의 두 행을 전부 찾아 고쳐야 합니다. 한 행만 고치면 같은 고객의 이메일이 행마다 달라지는 모순이 생깁니다.
- 삽입 이상 — 아직 주문을 한 번도 하지 않은 신규 고객의 정보를 등록하려면, 주문이 없으므로 이 테이블에는 넣을 방법이 없습니다.
- 삭제 이상 — 1002번 주문이 취소되어 그 행을 지우면, 이서연이라는 고객 정보 자체가 시스템에서 통째로 사라집니다.
정규화는 이 세 가지 이상현상을 없애기 위해, "하나의 사실은 하나의 자리에만 저장한다"는 원칙에 따라 테이블을 나누는 절차입니다.
1. 제1정규형(1NF) — 원자값과 반복 그룹 제거
1NF의 규칙은 단순합니다. 모든 컬럼 값이 더 이상 쪼갤 수 없는 원자값이어야 하고, 같은 성격의 값을 여러 컬럼에 반복해서 늘어놓지 않아야 합니다.
다음처럼 한 주문에 상품을 여러 개 담으려고 컬럼을 늘린 테이블을 생각해 봅시다.
| 주문번호 | 고객명 | 상품1 | 상품2 | 상품3 |
|---|---|---|---|---|
| 1001 | 김민준 | 키보드 | 마우스 | (없음) |
상품이 네 개인 주문이 들어오는 순간 컬럼이 모자랍니다. 상품3, 상품4를 계속 추가하는 방식은 확장에 한계가 있고, "상품이 몇 개인지" 조회하는 SQL도 지저분해집니다. 이런 반복 그룹은 행으로 풀어내야 합니다.
| 주문번호 | 고객명 | 상품명 |
|---|---|---|
| 1001 | 김민준 | 키보드 |
| 1001 | 김민준 | 마우스 |
한 셀에 여러 값을 콤마로 이어붙이는 것("키보드,마우스")도 1NF 위반입니다. 원자값이 아니기 때문에 특정 상품이 포함된 주문을 찾으려면 문자열을 파싱해야 하고, 인덱스도 제대로 걸리지 않습니다.
2. 제2정규형(2NF) — 부분 함수 종속 제거
2NF는 1NF를 만족하면서, 기본키가 복합키일 때 비주요 속성이 기본키 전체가 아니라 일부에만 종속되는 경우(부분 함수 종속)를 없애는 것입니다. 기본키가 단일 컬럼이면 2NF는 자동으로 만족되므로, 이 규칙은 복합키를 쓸 때만 의미가 있습니다.
수강신청 테이블을 예로 들어 보겠습니다. 기본키는 (학번, 과목코드)입니다.
| 학번 | 과목코드 | 성적 | 학생이름 | 담당교수 |
|---|---|---|---|---|
| 2024001 | DB301 | A+ | 박지훈 | 최교수 |
| 2024001 | OS201 | B0 | 박지훈 | 정교수 |
여기서 성적은 (학번, 과목코드) 조합 전체에 종속됩니다 — 같은 학생이라도
과목이 다르면 성적이 다를 수 있으니 정상입니다. 문제는 학생이름과
담당교수입니다. 학생이름은 학번만으로 정해지고,
담당교수는 과목코드만으로 정해집니다. 즉 기본키의 일부에만
종속되는 부분 함수 종속이 있는 것입니다. 이 상태에서는 같은 학생의 정보가 수강한
과목 수만큼 중복되고, 담당교수 정보도 그 과목을 수강하는 학생 수만큼 중복됩니다.
부분 종속을 제거하려면 세 테이블로 분리합니다.
CREATE TABLE students (
student_id VARCHAR(9) PRIMARY KEY,
name VARCHAR(50) NOT NULL
);
CREATE TABLE courses (
course_code VARCHAR(10) PRIMARY KEY,
professor VARCHAR(50) NOT NULL
);
CREATE TABLE enrollments (
student_id VARCHAR(9) NOT NULL REFERENCES students(student_id),
course_code VARCHAR(10) NOT NULL REFERENCES courses(course_code),
grade VARCHAR(2),
PRIMARY KEY (student_id, course_code)
);
이제 학생이름은 students에, 담당교수는 courses에 딱 한
자리씩만 존재합니다. 학생 이름이 바뀌어도 한 행만 고치면 됩니다.
3. 제3정규형(3NF) — 이행 함수 종속 제거
3NF는 2NF를 만족하면서, 비주요 속성이 기본키가 아니라 다른 비주요 속성을 통해서 간접적으로 종속되는 경우(이행 함수 종속)를 없애는 것입니다. 이번엔 기본키가 단일 컬럼인 직원 테이블을 예로 들어 보겠습니다.
| 사번 | 이름 | 부서코드 | 부서명 | 부서위치 |
|---|---|---|---|---|
| E001 | 한소희 | D10 | 개발팀 | 3층 |
| E002 | 오지환 | D10 | 개발팀 | 3층 |
부서명과 부서위치는 사번에 직접 종속되는 게
아니라, 사번 → 부서코드 → 부서명·부서위치처럼 부서코드를
거쳐서 간접적으로 정해집니다. 이게 이행 함수 종속입니다. 그 결과 같은 부서에 속한
직원 수만큼 부서명·부서위치가 중복되고, 부서 위치가 바뀌면 그 부서 직원 전원의
행을 찾아 고쳐야 합니다.
부서 정보를 별도 테이블로 분리하면 이 문제가 사라집니다.
CREATE TABLE departments (
dept_code VARCHAR(5) PRIMARY KEY,
name VARCHAR(50) NOT NULL,
location VARCHAR(50)
);
CREATE TABLE employees (
emp_id VARCHAR(5) PRIMARY KEY,
name VARCHAR(50) NOT NULL,
dept_code VARCHAR(5) NOT NULL REFERENCES departments(dept_code)
);
이제 부서 위치가 바뀌면 departments 테이블의 딱 한 행만 고치면 되고, 그 부서 소속 직원 전체에 자동으로 반영됩니다(조인으로 조회하니까요).
그 다음: BCNF와 실무 기준
이론적으로는 BCNF(보이스-코드 정규형), 4NF, 5NF까지 이어지지만, 이들은 후보키가 여러 개 겹치거나 다치 종속(multivalued dependency)이 있는 특수한 상황에서만 의미가 갈립니다. 실무 데이터 모델링에서는 3NF까지 정규화하는 것이 사실상의 표준입니다. 3NF를 만족하는 테이블 집합은 이상현상 없이 안전하게 갱신할 수 있고, 대부분의 업무 도메인은 그 이상의 정규형이 주는 이득이 거의 없습니다.
반정규화는 언제 하나
정규화가 갱신 안정성을 위한 것이라면, 반정규화(denormalization)는 조회 성능을 위한 의도적인 타협입니다. 정규화된 구조는 조인이 늘어나고, 조인이 늘어나면 대량 데이터에서 조회가 느려질 수 있습니다.
- 집계 컬럼 미리 저장 — 예를 들어 주문 테이블에 주문상세를
합산한
총액컬럼을 미리 저장해 두면, 매번 주문상세를 합산하는 조인 쿼리를 피할 수 있습니다. 대신 주문상세가 바뀔 때마다 총액을 함께 갱신해야 하는 책임이 생깁니다. - 자주 함께 조회되는 정보의 복사 — 게시글 목록에 작성자 이름을 매번 사용자 테이블과 조인하는 대신, 게시 시점의 작성자 이름을 게시글 테이블에 함께 저장하는 경우입니다(작성자가 나중에 닉네임을 바꿔도 과거 게시글에는 옛 이름이 남아야 하는 게시판이라면 오히려 이쪽이 정답입니다).
- 보고용 요약 테이블 — 원본은 정규화된 채로 두고, 통계·리포트용 테이블만 따로 비정규화된 형태로 별도 생성·갱신하는 방식입니다.
그려보면서 이해하기
정규화는 글로 읽는 것보다 직접 테이블을 쪼개 보면서 익히는 게 빠릅니다. YourERD 편집기에서 위 예제의 엔티티를 그려보면, 관계를 연결할 때마다 외래키가 자동으로 만들어지는 과정을 통해 "부모 키가 자식에 어떻게 전파되는지"를 눈으로 확인할 수 있습니다.