정규화
정규화는 반복되는 데이터(중복)를 없애서, 값 하나를 고칠 때 한 곳만 고치면 되도록 테이블을 나누는 과정입니다. 1NF → 2NF → 3NF로 갈수록 규칙이 하나씩 더 엄격해지고, 2NF·3NF 위반은 테이블을 나누어 해결합니다. 대신 조회할 때 JOIN이 늘어나는 트레이드오프가 있습니다.
핵심 개념
제1정규형 (1NF)
모든 컬럼이 원자값(하나의 값)만 가져야 합니다. 한 칸에 "노트북, 마우스"처럼 여러 값이 들어있으면 위반입니다.
핵심 개념
부분 함수 종속
기본키가 복합키(A, B)일 때, 키에 속하지 않는 컬럼이 A나 B 중 하나에만 의존하면 부분 함수 종속입니다.
핵심 개념
제2정규형 (2NF)
1NF를 만족하면서, 부분 함수 종속이 없어야 합니다(후보키가 하나인 경우를 기준으로 한 설명입니다). 부분 종속되는 컬럼을 별도 테이블로 분리해서 해결합니다.
핵심 개념
이행 종속
키 A가 B를 결정하고, 키가 아닌 B가 다시 키가 아닌 C를 결정할 때(B는 A를 결정하지 못함) C는 A에 이행 종속입니다.
핵심 개념
제3정규형 (3NF)
2NF를 만족하면서, 이행 종속이 없어야 합니다. 키에 속하지 않는 모든 컬럼은 키에 "직접" 의존해야 합니다(후보키가 하나인 경우를 기준으로 한 설명입니다).
핵심 개념
정규화의 대가
테이블을 나눌수록 중복은 줄지만, 데이터를 한 번에 보려면 JOIN이 필요해집니다. 그래서 조회 성능을 위해 일부러 중복을 허용하는 반정규화를 쓰기도 합니다.
정규화가 해결하려는 문제: 이상 현상
정규형은 에드거 코드(E. F. Codd)가 1970년 관계형 모델 논문에서 제1정규형 개념을 제시한 뒤 이어서 확장된 개념입니다. 중복이 있는 테이블에서는 이상 현상(anomaly)이 생깁니다. 예를 들어 주문 테이블 한 행마다 상품명이 함께 저장되어 있다고 해 봅시다.
함수 종속과 BCNF
정규형은 함수 종속(functional dependency)으로 정의됩니다. A 값을 알면 B 값이 하나로 정해질 때 A → B라고 씁니다. 3NF 다음으로 자주 거론되는 보이스-코드 정규형(BCNF)은 자명하지 않은 모든 함수 종속의 결정자(→ 왼쪽)가 슈퍼키(후보키를 포함하는 컬럼 집합)여야 한다는 조건입니다. 후보키가 여러 개이고 서로 겹칠 때처럼, 3NF를 만족해도 슈퍼키가 아닌 결정자가 남는 경우가 있고, BCNF는 그것까지 없앱니다.
자주 하는 오해
면접에서 이렇게 물어요
면접 질문
제2정규형과 제3정규형은 무엇이 다른가요?
후보키가 하나인 경우를 기준으로 설명합니다. 2NF는 키의 일부에만 의존하는 컬럼(부분 함수 종속)을 없앱니다. 3NF는 키가 아닌 컬럼을 거쳐 다른 키가 아닌 컬럼이 결정되는 관계(이행 종속)를 없앱니다. 둘 다 키에 속하지 않는 컬럼이 키 전체에 직접 의존하게 만드는 과정입니다.
면접 질문
정규화를 하는 이유와 하지 않는 경우를 말해 보세요.
이상 현상(갱신·삽입·삭제)을 막고 중복을 줄이기 위해서입니다. 읽기가 대부분이고 JOIN 비용이 성능 문제일 때, 또는 분석용 데이터처럼 변경이 거의 없을 때 반정규화를 쓰기도 합니다.
면접 질문
BCNF는 3NF와 무엇이 다른가요?
3NF는 후보키에 속한 컬럼(주 속성)이 결정되는 경우를 허용하지만, BCNF는 자명하지 않은 모든 종속의 결정자가 슈퍼키일 것을 요구합니다. 그래서 BCNF가 3NF보다 엄격합니다.
면접 질문
반정규화는 어떻게 안전하게 하나요?
정규화된 구조를 기준으로 두고 필요한 부분에만 중복 컬럼이나 요약 테이블을 추가합니다. 중복한 값이 서로 어긋나지 않게 하는 방법이 함께 필요합니다.
실제 시스템에서는
함께 보면 좋은 토픽
참고 자료
SQL — 정규화 전(비정규형) vs 정규화 후(3NF)
-- 정규화 전: 한 테이블에 모든 게 뒤섞여 있고, products는 콤마로 여러 값 CREATE TABLE orders_raw ( order_id BIGINT PRIMARY KEY, user_name VARCHAR(50), user_grade VARCHAR(10), products VARCHAR(200) -- '노트북, 마우스' — 1NF 위반 ); -- 정규화 후(3NF): 세 테이블로 분리, 각 non-key 컬럼은 자기 테이블의 PK에만 의존 CREATE TABLE users ( user_name VARCHAR(50) PRIMARY KEY, user_grade VARCHAR(10) NOT NULL ); CREATE TABLE orders ( order_id BIGINT PRIMARY KEY AUTO_INCREMENT, user_name VARCHAR(50) NOT NULL, FOREIGN KEY (user_name) REFERENCES users(user_name) ); CREATE TABLE order_items ( order_id BIGINT NOT NULL, product VARCHAR(50) NOT NULL, PRIMARY KEY (order_id, product), FOREIGN KEY (order_id) REFERENCES orders(order_id) );
이 시각화의 3NF 탭 결과와 정확히 같은 구조입니다 — users.user_grade는 더 이상 주문 정보에 끌려다니지 않고, 사용자 한 명당 딱 한 번만 저장됩니다.
김민준의 등급이 바뀌면 users 테이블 딱 한 행만 UPDATE하면 됩니다 — 정규화 전이었다면 김민준의 주문이 100건이면 100행을 다 고쳐야 했을 것입니다.
Java — 정규화된 구조를 그대로 반영한 JPA 엔티티
@Entity public class User { @Id private String userName; private String userGrade; } @Entity public class Order { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_name") private User user; // user_grade는 여기 없다 — User를 통해서만 접근 @OneToMany(mappedBy = "order", cascade = CascadeType.ALL) private List<OrderItem> items = new ArrayList<>(); } @Entity public class OrderItem { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne(fetch = FetchType.LAZY) private Order order; private String product; }
Order에는 userGrade 필드가 없습니다 — 등급이 필요하면 반드시 order.getUser().getUserGrade()처럼 User를 거쳐야 합니다.
이렇게 필드를 직접 두지 않는 것 자체가 "이행 종속을 만들지 않는다"는 3NF 규칙을 코드로 강제하는 방법입니다.
Java — (참고) 조회 성능을 위한 반정규화
@Entity public class Order { // ... // 원칙적으로는 3NF 위반(이행 종속)이지만, // "주문 목록에 등급 뱃지 표시"가 매번 JOIN을 유발해 느리다면 // 의도적으로 복제해 두는 경우가 있다 (반정규화) private String userGradeSnapshot; }
이 시각화의 "정규화의 대가" 카드에서 언급한 트레이드오프가 바로 이것입니다 — userGradeSnapshot은 등급이 바뀌면 여러 곳을 고쳐야 하는 대신, 조회할 때 User와의 JOIN을 피할 수 있습니다.
이런 선택은 "정규화 규칙을 모르고 어긴 것"과 "알고서 성능을 위해 의도적으로 어긴 것"의 차이이므로, 반정규화는 항상 팀 안에서 합의하고 문서로 남겨야 합니다.
Q1
정규화(Normalization)의 목적은?
Q2
제1정규형(1NF)을 만족하기 위한 조건은?
Q3
제2정규형(2NF)에서 다루는 문제는?