Normalisation — The Rules That Remove Update Anomalies
한국어 원문으로 표시합니다.
한 줄 요약
정규화는 같은 사실이 여러 자리에 적히지 않게 표를 나누는 절차이고, 그렇게 하는 이유는 저장 공간이 아니라 갱신 이상을 없애기 위해서다.
왜 이게 필요했나
주문 표 하나에 고객 이름과 주소까지 함께 적어 두었다고 하자. 세 가지 문제가 생긴다.
- 갱신 이상 — 고객이 이사하면 그 고객의 모든 주문 행을 고쳐야 한다. 하나라도 빠뜨리면 같은 고객의 주소가 두 가지가 된다.
- 삽입 이상 — 아직 주문하지 않은 고객은 등록할 자리가 없다.
- 삭제 이상 — 마지막 주문을 지우면 그 고객 정보까지 함께 사라진다.
정규화는 이 세 이상을 없애는 절차다. 공간을 아끼는 것은 부수 효과일 뿐이다.
어떻게 동작하나
실무에서 쓰이는 것은 대체로 3정규형까지다.
- 1정규형 — 모든 속성이 원자값이어야 한다. 한 칸에 쉼표로 여러 값을 넣지 않는다.
- 2정규형 — 1정규형이면서, 기본 키의 일부에만 종속된 속성이 없어야 한다. 복합 키일 때만 문제가 된다. 예를 들어
(주문번호, 상품번호)가 키인데상품명이 상품번호에만 종속되면 분리한다. - 3정규형 — 2정규형이면서, 키가 아닌 속성에 종속된 속성이 없어야 한다.
우편번호가도시를 결정한다면 그 관계를 별도 표로 뺀다.
핵심은 규칙의 형태가 아니라 하나의 사실은 한 자리에만 적는다는 원리다. 이 원리를 지키면 갱신이 항상 한 곳에서 끝나므로 불일치가 구조적으로 불가능해진다.
함수 종속을 손으로 찾아보기
정규형의 정의는 전부 함수 종속 이라는 한 개념 위에 서 있습니다. A → B 는
"A 가 정해지면 B 가 하나로 정해진다" 는 뜻입니다. 표를 놓고 이 화살표를 그려 보면
정규형 판정이 기계적으로 됩니다.
주문 표를 예로 들겠습니다.
주문상세(주문번호, 상품번호, 수량, 상품명, 단가, 고객번호, 고객명, 고객주소)
키: (주문번호, 상품번호)
화살표를 그려 보면:
(주문번호, 상품번호) → 수량 ← 키 전체에 종속. 정상
상품번호 → 상품명, 단가 ← 키의 "일부" 에만 종속. 2정규형 위반
주문번호 → 고객번호 ← 키의 "일부" 에만 종속. 2정규형 위반
고객번호 → 고객명, 고객주소 ← 키가 아닌 것에 종속. 3정규형 위반
화살표대로 표를 쪼갭니다. 왼쪽이 새 표의 키가 됩니다.
주문상세(주문번호, 상품번호, 수량)
상품(상품번호, 상품명, 단가)
주문(주문번호, 고객번호, 주문일)
고객(고객번호, 고객명, 고객주소)
이 절차에 판단이 들어갈 자리는 하나뿐입니다 — 화살표가 실제로 성립하는가. "우편번호 → 도시" 는 한국에서는 대체로 맞지만 예외가 있고, 미국에서는 ZIP 하나가 여러 도시에 걸치기도 합니다. 도메인 지식이 필요한 지점이 여기입니다.
정규형 요약표
| 정규형 | 없애는 것 | 한 줄 판정 |
|---|---|---|
| 1NF | 반복 그룹, 다중값 | 한 칸에 값이 하나인가 |
| 2NF | 부분 함수 종속 | 복합 키의 일부 에만 매달린 컬럼이 있나 |
| 3NF | 이행 함수 종속 | 키가 아닌 컬럼이 다른 컬럼을 결정하나 |
| BCNF | 후보 키가 얽힌 예외 | 모든 결정자가 후보 키인가 |
BCNF 는 3NF 를 만족하는데도 이상이 남는 드문 경우를 잡습니다. 후보 키가 여럿이고 서로 겹칠 때 생기며, 실무에서 만나는 빈도는 낮습니다. 3NF 까지가 실전 입니다.
정규화가 성능을 해친다는 오해
"정규화하면 조인이 늘어 느려진다" 는 말은 절반만 맞습니다. 조인은 늘지만, 정규화된 표는 행이 짧아 한 페이지에 더 많이 들어가고 인덱스도 작습니다. 갱신은 한 곳만 건드리므로 훨씬 빠릅니다.
실제로 느려지는 경우는 대개 인덱스가 없는 것 이지 정규화 자체가 아닙니다. 외래 키 컬럼에 인덱스가 없으면 조인마다 전체 탐색이 일어납니다. PostgreSQL 은 기본 키에는 인덱스를 자동으로 만들지만 외래 키에는 만들지 않습니다. 이것을 모르고 "정규화 때문에 느리다" 고 결론 내리는 일이 흔합니다.
현장에서 만나는 모습
정규화는 기본값이지 종교가 아니다. 깨야 할 때가 있고, 그때 무엇을 대가로 내는지 알고 깨야 한다.
역정규화가 정당화되는 전형적인 경우는 집계값의 물리화다. 게시글의 댓글 수를 매번 세는 대신 컬럼에 유지하는 식이다. 대가는 분명하다. 두 곳의 값이 어긋날 수 있고 그것을 막는 책임이 애플리케이션으로 넘어온다. 어긋나는 경로는 하나가 아니다. 트리거를 우회한 대량 삭제, 트리거를 잠시 껐던 마이그레이션, 여러 코드 경로 중 하나의 누락.
그래서 역정규화를 도입할 때는 세 가지를 함께 남겨야 한다. 왜 깼는지, 무엇이 정합성을 보장하는지, 그리고 어긋났을 때 바로잡는 재계산 쿼리다. 세 번째를 미리 써 두지 않으면 사고 대응 중에 급조하게 된다.
인기 게시글에 댓글이 몰릴 때 같은 행을 모두가 갱신하려 해 잠금 경합이 생긴다는 점도 알아 둘 만하다. 대안은 증분을 별도 표에 추가만 하고 주기적으로 합산하거나, 애초에 정확한 실시간 값이 필요한지 다시 묻는 것이다. 대부분의 카운터는 5초 늦어도 아무 일도 일어나지 않는다.
이어지는 퀴즈에서 확인할 것
정규형의 정의를 외우는 대신, 어떤 이상 현상을 없애려는 것인지와 언제 깨는 것이 합리적인지 판단할 수 있는지 확인한다.