1. 제1정규형: 모든 속성은 반드시 하나의 값을 가져야 한다
- 하나의 속성에는 하나의 값을 가져야지만 제1정규형을 만족시킴
- 아래 모델과 같이 연락처 속성에 다중 값(multivalued)이 들어가는 경우 다음과 같은 문제점이 발생할 수 있음
- 연락처 정보에서 집전화 번호와 핸드폰 번호를 구별하기 어려움
- A 고객은 집전화가 여러 대고, B 고객은 핸드폰인 여러 대라면 혼재된 속성에서 원하는 속성 값을 추출하기 어려움
- 명확하지 않은 속성은 이메일처럼 다른 유형의 데이터를 포함할 수도 있어 본연의 의미가 퇴색될 수 있음
- 데이터를 아래와 같이 관리한다면 개발 복잡성이 증가하고, 연락처 속성에 담긴 본래의 의미가 점차 퇴색될 것
고객번호 | 고객명 | 연락처 |
10000 | 정우진 | 02-123-4567, 010-1234-5678 |
10001 | 한형식 | 010-5678-2345 |
10002 | 황영은 | 02-345-3456, 010-4567-7890 |
- 다음과 같이 고객연락처라는 엔티티를 추가하면 다중 값에 대한 문제점을 해결할 수 있음
- 고객의 연락처가 많아져도 문제가 발생하지 않음
- 이처럼 다중 값을 제거함으로써 속성을 더 명확하게 활용할 수 있으며 개발 복잡성을 감소시킬 수 있음
고객번호 | 고객명 |
10000 | 정우진 |
10001 | 한형식 |
10002 | 황영은 |
고객번호 | 순번 | 연락처 |
10000 | 1 | 02-123-4567 |
10000 | 2 | 010-1234-5678 |
10001 | 1 | 010-5678-2345 |
10002 | 1 | 02-345-3456 |
10002 | 2 | 010-4567-7890 |
- 제1정규형은 다중 값 말고도 다른 유형의 중복 데이터도 의미할 수 있으며 이런 경우 중복 데이터를 속성으로 분리함에 따라 해결할 수 있음
- 아래 모델은 주문 엔터티로 주문이 발생했을 때의 정보를 관리하는데 해당 모델을 보고 우려되는 점은 다음과 같음
- 상품을 세 개 이상 주문할 수 없음
- 상품 1, 상품 2 모두 빠르게 조회하고 싶을 경우 속성마다 인덱스를 추가해야 됨
- 다음과 같이 주문상세 엔티티를 추가할 경우 앞서 언급한 문제를 해결할 수 있음
- 상품을 몇 개를 주문하던 아무런 제약을 받지 않음
- 추가적인 인덱스 또한 필요 없음
2. 제2정규형: 엔티티의 일반속성은 주식별자 전체에 종속적이어야 한다
- 앞서 [그림 I -2-4]의 주문상세 모델을 보면 `상품명` 속성이 주식별자가 아닌 오직 상품번호에 대해서만 반복되어 쌓이게 되는 구조인 것을 확인할 수 있음
- 아래 표에서 `SQL 전문가 가이드`라는 데이터가 반복되는 것을 확인 가능
- 상품번호도 중복되지만 고객이 상품을 주문함으로써 발생하는 매핑정보로서 의미를 가지고 있고 주문번호와 함께 주문상세 엔티티의 식별자 의미를 가지고 있기에 중복된 데이터라고 볼 수 없음
- 반면, 상품명은 주문번호와 관계없이 오직 상품번호에 의해서만 결정되므로 상품명이 주식별자인 `주문번호 + 상품번호`가 아닌 오직 상품번호에 `종속적이다`라고 표현 가능
주문번호 | 상품번호 | 상품명 |
1100001 | 256 | SQL 전문가 가이드 |
1100002 | 257 | 데이터아키텍처 전문가 가이드 |
1100003 | 256 | SQL 전문가 가이드 |
1100004 | 256 | SQL 전문가 가이드 |
1100005 | 258 | 데이터 분석 전문가 가이드 |
- 함수종속성 (Functional Dependency)은 데이터들이 어떤 기준값에 의해 종속되는 현상을 지칭
- 기준값을 결정자 (Determinant)라 하고, 종속되는 값을 종속자 (Dependent)라고 함
- 앞선 표에서 상품명은 상품번호에 종속되어 있기에 종속자이며, 상품번호는 상품명을 결정하기에 결정자
- 주문상세 엔티티의 상품명은 식별자 전체가 아닌 일부에만 종속적이며 이를 부분 종속 (Partial Dependency)라고 함
- 이는 `엔티티의 일반속성은 부식별자 전체에 종속적이어야 한다`는 제 2정규형을 위배한 것이며 다음과 같은 문제점을 가짐
- 상품명이 변경되고 업무적으로 반영해주어야 할 경우 주문상세의 중복된 상품명을 모두 변경해야 함
- 주문상세의 상품명을 변경한다고 해도 특정 시점에는 아직 변경되지 않은 상품명이 존재하고, 이때 들어온 트랜잭션은 일관되지 않은 데이터를 조회
- 아래와 같이 상품 엔티티를 추가하면 주문상세 엔티티의 부분 종속성을 제거할 수 있음
- 상품명 속성을 상품 엔티티에서 관리하고 상품번호를 매핑키로 활용하여, 상품명을 확인하는 구조로 데이터를 일원화해 관리함으로써 앞서 제시한 문제점을 해결할 수 있음
- 이로써 `일반속성은 주식별자 전체에 종속해야 한다`는 제2정규형을 만족하게 됨
- 이렇게 데이터를 관리하면 주문상세 엔티티에서는 상품번호만 들고 있고, 상품번호를 매핑키로 상품 엔티티에서 원하는 상품정보 데이터를 가져올 수 있음 (JOIN)
상품번호 | 상품명 |
256 | SQL 전문가 가이드 |
257 | 데이터아키텍처 전문가 가이드 |
258 | 데이터 분석 전문가 가이드 |
주문번호 | 상품번호 |
1100001 | 256 |
1100002 | 257 |
1100003 | 256 |
1100004 | 256 |
1100005 | 258 |
3. 제3정규형: 엔티티의 일반속성 간에는 서로 종속적이지 않는다
- [그림 I-2-6]에서 고객번호는 주문번호에 종속적이고, 고객명은 고객번호에 종속적
- 이는 `고객명이 주문번호에 종속적`임을 의미
- 이것을 이행적 종속 (Transitive Dependency)이라 하고, 이행적 종속을 배제하는 것을 제3정규형이라고 함
주문번호 -> 고객번호 -> 고객명 => 주문번호 -> 고객명
- 고객번호와 고객명 모두 주문번호에 종속하며 제2정규형은 만족하였으나, 고객명이 식별자가 아닌 일반속성에 종속적인 제3정규형 위배에 해당
- 만약 고객이 개명하여 고객명이 변경되었다면, 주문 엔티티에 고객명을 전부 갱신해야 하는데 이는 주문과는 전혀 연관 없는 트랜잭션
- 데이터 중복으로 인해 발생하는 문제는 성능 부하 및 정합성 오류로 제2정규형과 동일
- 아래와 같이 고객 엔티티를 분리하여 관리하면 위 문제를 해결할 수 있음
- 아래 모델의 고객 엔티티를 보면 고객 속성 변경이 주문 엔티티에 영향을 주지 않는 구조이며 데이터 중복에 대한 문제도 개선되었다고 볼 수 있음
- 비회원도 주문이 가능한 서비스이므로 고객번호가 Nullable
정리
- 정규화는 필수적이지만 무조건적이지는 않음
- 상황에 따라서는 반정규화를 진행할 수도 있음
- 하지만 중요한 것은 기본적으로 정규화를 진행한 뒤 반정규화를 고려해야 한다는 점
- 기본적으로 정규화를 진행해야지 무분별한 반정규화를 방지하고 무심코 놓칠 수 있는 부분도 챙길 수 있음
4. 반정규화와 성능
- 반정규화는 정규화를 반대로 하는 것으로 역정규화라고도 부름
- 정규화는 데이터의 중복을 최소화했다면, 반정규화는 성능을 위해 데이터 중복을 허용하는 것
- 주로 성능이 문제 될 때 반정규화에 대한 논의가 진행됨
- 반정규화를 진행하면 조회 성능을 향상할 수 있겠지만 입력, 수정 및 삭제 성능은 저하될 수 있으므로 심사숙고해서 진행해야 됨
가. 반정규화를 적용한 모델에서 성능이 향상될 수 있는 경우
- 아래 모델에서 고객의 편의를 위해 최근 신용카드 정보를 미리 세팅하는 요건일 경우 다음과 같이 SQL을 작성하는데 다음과 같은 성능 문제가 존재함
- 최종결과는 1건을 가져오지만, 주문내역이 많을수록 해당 주문테이블과 결제테이블의 조인 건수가 증가하게 되며, 조인된 결제정보를 모두 읽고 내림차순 정렬하여 최근 1건의 데이터를 가져오기 때문에 1234 고객의 주문 내역이 많아질수록 성능이 나빠짐
- 즉, 주문내역이 많을수록 조인에 대한 부하가 증가하여 성능이 나빠지는 구조
- 결제 엔티티에 고객번호 속성을 반정규화함으로써 조인에 대한 성능 부하를 개선할 수 있음
- 결제 테이블에 `고객번호 + 결제수단구분코드 + 결제일시`로 인덱스를 생성하고 `Index Range Scan Descending`으로 최종 1건의 데이터만 읽어 결제수단번호를 가져올 수 있음
- 최적의 SQL로 성능 부하를 극적으로 개선할 수 있음
나. 반정규화를 적용한 모델에서 성능이 저하될 수 있는 경우
- 아래 모델에서 주문정보는 주문 엔티티에서 가져올 수 있고, 송장번호는 배송 엔티티에서 가져올 수 있기 때문에 주문과 배송 엔티티를 함께 조인해야 함
- 앞서 반정규화를 통해 성능을 향상했으므로, 이번에도 주문 엔티티에 송장번호를 반정규화하여 성능 개선을 도모했다고 가정
- 더 이상 배송 엔티티와 조인을 하지 않아도 되기 때문에 조회 관점에서는 더 빠른 성능을 확보할 수 있었음
- 위와 같이 반정규화를 진행함에 따라 주문 모델의 송장번호는 주문 시점에는 NULL 데이터가 들어가며, 배송 준비가 완료되어야 송장번호를 UPDATE 할 수 있음
- 조회 성능 향상을 위해 반정규화를 하기 전에는 없었던 UPDATE 로직이 새로 추가
- 정규화한 모델 (그림 I-2-11)에 적절한 인덱스가 구성되었다고 가정했을 경우 반정규화한 모델 (그림 I-2-12)이 가지는 이점은 사실 굉장히 미미할 것
정리
- 반정규화는 데이터 불일치로 인한 정합성 문제뿐 아니라, 불필요한 트랜잭션으로 인한 성능 문제를 생성하기 때문에 반정규화는 꼭 필요할 때만 적용해야 함
- 조회 성능에서 미미한 이점을 취하고, 불필요한 UPDATE로 인해 또 다른 성능을 손해 본다면 이는 합리적인 판단이라고 할 수 없음
- 정리하면 반정규화는 `그럼에도` 진행해야 할 만한 근거가 뒷받침될 때 비로소 진행해야 함
참고
SQL 전문가 가이드 2020 개정판 - 한국데이터산업진흥원
반응형
'DB > SQL 전문가 가이드' 카테고리의 다른 글
[과목 I 2장 3절] 모델이 표현하는 트랜잭션의 이해 (0) | 2025.03.17 |
---|---|
[과목 I 2장 2절] 관계와 조인의 이해 (1) | 2025.03.17 |
[과목 I 1장 5절] 식별자 (0) | 2025.02.23 |
[과목 I 1장 4절] 관계 (0) | 2025.02.22 |
[과목 I 1장 3절] 속성 (0) | 2025.02.22 |