[데이터베이스]정규화 복습
목적: 데이터베이스 정규화와 관련된 내용을 복습하고 이해.
정규화란 관계형 모델에서 논리 스키마를 효과적으로 모델링하기 위해 이용하는 기법. 정규형을 분석함으로써 릴레이션의 스키마가 얼마나 효율적으로 실세계를 반영하고 있는지 평가할 수 있다.
정규화란 개념을 모르고 스키마를 만들때 프론트엔드에서 요청할 때 이런 데이터들이 필요하니까 이 모델에는 이런 스키마가 필요하겠다.라고 해서 만들었는데, 내가 만든 스키마를 보면 정규화가 무슨 역할을 하는지 모두 이해할 수 있다.
정규화의 효용성
1. 한 릴레이션 내의 컬럼들 간의 관계 표현
2. 불필요한 데이터의 종속과 중복 제거
3. 새로운 컬럼 추가 시 기존 컬럼과의 관계 수정을 최소화
함수적 종속성이란?
속성들 간의 값의 연관관계를 표현한 것.
잘못 설계된 릴레이션을 구성하는 속성들 간의 연관관계를 검사하고, 이를 기반으로 릴레이션이 특정 정규형을 만족하도록 정규화하는 역할을 함.
임의의 릴레이션 스키마 R의 인스턴스에 포함되는 서로 다른 레코드 r1, r2와 컬럼 X, Y에 대해 r1[X]=r2[X] 일 때, r1[Y] = r2[Y]이면 함수적 종속성 X->Y가 성립한다.
말이 어려운데, 하나의 릴레이션에 컬럼값이 다른 컬럼의 값을 종속한다면 이것을 함수적 종속이라고 한다. 생각해 보면 한 컬럼값에 따라 다른 컬럼값이 완전히 종속되어 있다면 하나는 당연히 필요가 없다. 예를 들어, 회원등급이 할인율을 결정한다면 굳이 한 스키마에 두 컬럼이 둘 다 있을 필요는 없다. 그냥 할인율 테이블을 하나 만들고 회원등급과 관계를 맺어주고 스키마에서는 회원등급만 관리하면 되겠지.
정규형이란?
갱신 이상 현상을 최소화하도록 특정 조건을 갖춘 릴레이션의 형식.
제1정규형에서 제5 정규형으로 갈수록 정규화 조건이 까다롭고 수준이 높아진다.
정규화의 목적
1. 어떠한 릴레이션이라도 데이터베이스 내에서 효과적으로 표현할 수 있도록 만든다.
2. 검색 알고리즘을 효과적으로 만든다.
3. 릴레이션에서 삽입,갱신,삭제 등의 바람직하지 않은 이상현상을 제거한다.
4. 새로운 형태의 데이터가 삽입될 때 릴레이션을 재구성할 필요성을 줄인다.
제1 정규형
어떤 릴레이션 스키마에서 정의된 모든 속성의 도메인이 원자값을 가지는 조건을 만족하면 제1 정규형이다.
관계형 모델의 조건을 잘 따랐다면 자연스럽게 만족하게 되어 제1 정규형이 된다.
학생ID | 학생이름 | 수강과목 |
1 | john doe | Math,Science |
2 | jane smith | History, Math, English |
3 | mike johnson | Science |
수강과목이 원자값을 가지지 않는다
학생ID | 학생이름 | 수강과목 |
1 | john doe | Math |
1 | john doe | Science |
2 | jane smith | History |
2 | jane smith | Math |
2 | jane smith | English |
3 | mike johnson | Science |
모든 컬럼이 원자값을 가진다.
책에는 없지만 검색해 보니 각 행(레코드)은 유일한 식별자(기본키)를 가져야 한다는 조건도 있다.
위의 예제는 단지 원자값을 가져야 한다! 에만 적합한 예제이고 각 행에 유일한 기본 키를 가지고 있지는 않다.
유일한 식별자(기본 키)를 부여하기 위해서는 학생 ID와 학생이름으로 이루어진 릴레이션, 과목 ID와 과목이름으로 이루어진 릴레이션을 각각 구성하고 다대다 연결을 해야 한다.
그래야 모든 테이블의 레코드가 원자값이며, 유일한 기본키를 가지게 된다. 위의 예제는 원자값이란 게 무엇인지만 이해하는 용도이다.
제2 정규형
제1 정규형을 만족하고 기본키가 아닌 속성들이 기본키에 완전 함수 종속되면 제2 정규형이다.(부분 함수적 종속 제거)
부분 함수적 종속
속성 집합 Y가 속성 집합 X의 전체가 아닌 일부분에 함수적 종속되어 있는 상태
완전 함수적 종속
속성 집합 Y가 속성 집합 X의 전체에 함수적으로 종속되는 상태
쉽게 말하면 부분 함수적 종속은 기본키 전체에 함수적으로 종속되는 게 아니라 기본키의 일부(부분적)에 함수적으로 종속된다는 뜻이다.
orderID(PK) | productID(PK) | orderDate | productName | customerName |
1 | A | 2024-01-01 | goods1 | john doe |
1 | B | 2024-01-01 | goods2 | jane smith |
2 | A | 2024-01-02 | goods1 | mike johnson |
위 릴레이션에서 기본키는 orderID와 productID 둘 다 사용한다. 근데 orderDate는 orderID에만 함수적으로 종속되어 있다.
반면에 productName은 productID에만 함수종속되어 있다. 그러므로 orderDate, productName둘다 부분종속이므로 제거되어야 한다.
orderID | orderDate |
1 | 2024-01-01 |
2 | 2024-01-02 |
productID | productName |
A | goods1 |
B | goods2 |
orderID(PK) | productID(PK) | customerName |
1 | A | john doe |
1 | B | jane smith |
2 | A | mike johnson |
제2 정규형을 준수하며 분해된 테이블을 조인 시 원래의 릴레이션으로 복원 가능한 분해를 무손실 분해라고 한다.
제3 정규형
제2 정규형을 만족하고 기본키가 아닌 속성들이 어떤 키에도 이행적으로 종속되지 않는다.
이행적 종속이란 A->B, B->C 일 때 A->C인 함수적 종속을 말한다.
이해해 보자
기본키인 A컬럼값이 B를 함수적 종속한다. 기본키인 A컬럼값이 C를 함수적 종속한다.
이런 상황이라면 컬럼값 B가 C를 함수적으로 종속한다는 것은 중복이라고 이해할 수 있다.
이런 것을 이행적 종속이라고 한다. 모든 속성들이 오직 기본키에 의해서만 결정되어야 한다는 의미.
책에 나온 예제로 이해해 보자
도크번호(PK) | 입항시간(PK) | 출항시간 | 목적 | 담당도선사 |
D1 | 09:00 | 10:15 | 선적 | 김씨 |
D1 | 11:00 | 11:45 | 선적 | 김씨 |
D1 | 11:50 | 12:45 | 하역 | 구씨 |
D2 | 09:00 | 10:00 | 관광 | 이씨 |
D2 | 12:00 | 12:45 | 주유 | 이씨 |
D2 | 13:00 | 15:00 | 정비 | 윤씨 |
{도크번호, 입항시간} -> {목적}
{목적}->{담당도선사}
{도크번호, 입항시간}->{담당도선사}
즉 이행적종속에 의해 A->B , B->C, A->C 므로 B, C 간 릴레이션을 하나 분리해야 한다.
도크번호(PK) | 입항시간(PK) | 출항시간 | 목적 |
D1 | 09:00 | 10:15 | 선적 |
D1 | 11:00 | 11:45 | 선적 |
D1 | 11:50 | 12:45 | 하역 |
D2 | 09:00 | 10:00 | 관광 |
D2 | 12:00 | 12:45 | 주유 |
D2 | 13:00 | 15:00 | 정비 |
목적 | 담당도선사 |
선적 | 김씨 |
하역 | 구씨 |
관광 | 이씨 |
주유 | 이씨 |
정비 | 윤씨 |
BC정규형
제3 정규형을 만족하고 릴레이션에서 성립하는 X->Y형태의 모든 함수적 종속성에 대하여 X가 슈퍼키이면 BC정규형
슈퍼키는 한 개 이상의 컬럼으로 구성될 수 있으며, 테이블 내의 레코드를 유일하게 식별할 수 있다. 최소성을 만족시키지 않아도 되므로 필요 이상의 정보를 가지고 있을 수도 있다.
후보키는 슈퍼키와 동일하게 모든 레코드의 유일성을 식별할 수 있으며 최소성을 만족해야 한다.
기본키는 후보키 중에 후보키 중에 선택된 각 레코드를 유일하게 식별할 수 있는 속성이다.
BC정규형을 만족하지 않는 릴레이션을 보자
도크번호(PK) | 입항시간(PK) | 출항시간 | 목적 |
D1 | 09:00 | 10:15 | 선적 |
D1 | 11:00 | 11:45 | 선적 |
D1 | 11:50 | 12:45 | 하역 |
D2 | 09:00 | 10:00 | 관광 |
D2 | 12:00 | 12:45 | 주유 |
D2 | 13:00 | 15:00 | 정비 |
수퍼키인{도크번호, 입항시간} -> {출항시간} 이고 {도크번호, 입항시간} -> {목적}이다.
위 두 개는 모든 결정자가 슈퍼키이므로 BC정규형에 문제가 없다.
하지만 {목적}->{도크번호} 의 목적은 슈퍼키가 아니므로 BC정규형을 만족하지 못한다.
위의 경우에는 도크번호가 분해대상이 되는데, 분해 대상이 되면 위 릴레이션의 기본키를 유지하지 못하므로 기본키를 변경한다.
목적이 도크번호를 종속하므로 도크번호(PK)가 목적으로 변경된다.
그리고 목적, 도크번호로 이루어진 릴레이션을 하나 만든다.
목적(PK) | 입항시간(PK) | 출항시간 |
선적 | 09:00 | 10:15 |
선적 | 11:00 | 11:45 |
하역 | 11:50 | 12:45 |
관광 | 09:00 | 10:00 |
주유 | 12:00 | 12:45 |
정비 | 13:00 | 15:00 |
목적(PK) | 도크번호 |
선적 | D1 |
하역 | D1 |
관광 | D2 |
주유 | D2 |
정비 | D2 |
역정규화
애써 정규화한 릴레이션을 왜 다시 역정규화 하느냐. 정규화를 통해 데이터의 중복을 제거할 수는 있지만 사용과정에서 과도한 조인 비용이 추가적으로 발생할 수 있다. 이것은 Query Response 속도 지연으로 이어질 수 있다. 그래서 역정규화가 필요하다.
정보의 부분적 중복을 허용하지만 데이터 접근 성능이 개선된다.
SELECT문의 성능 향상
INSERT, UPDATE, DELETE문의 속도 저하(중복된 데이터의 일관성 유지를 위해)