함수 종속성 (Functional Dependency)
함수 종속성(Functional Dependency)
- 특정 속성이 다른 속성을 결정하는 관계를 의미
- X -> Y == X가 Y를 함수적으로 결정 == X의 값이 정해지면 Y의 값이 항상 고유하게 결정된다
- 잘못 설계된 함수 종속성을 정규화를 통해 제거하면 데이터 무결성 보장, 중복 제거, 저장 공간 절약, 성능 개선 등의 이점이 있다
- ex) '사원' 테이블 == '사원번호' -> '이름' 의 함수 종속성이 존재
: '사원번호'가 정해지면 그에 해당하는 고유한 '이름'이 정해지지만, '이름'은 여러 개가 존재할 수 있으므로 이에 대응하는 '사원번호'가 유일하지 않을 수 있기 때문
함수 종속성의 유형
- 완전 함수 종속 (Full Functional Dependency)
- 어떤 속성이 기본 키의 일부가 아니라 '전체'에 종속된 경우
- ex)
- 기본키 = '학번' + '수업코드'
- 속성 = '성적'
- 함수 종속성 = '학번' + '수업코드' -> '성적'
: '학번'과 '수업코드' 둘 다가 주어져야 그에 대응하는 고유한 '성적'이 결정 됨
- 부분 함수 종속 (Partial Dependency)
- 기본 키의 일부에 의해 결정되는 경우
- 제2정규형을 통해 제거할 수 있다
- ex)
- 기본키 = '학번' + '수업코드'
- 속성 = '이름'
- 함수 종속성 = '학번' -> '이름'
: '이름'은 '학번'만이 주어져도 고유하게 결정되므로 부분 함수 종속이다
- 이행 함수 종속 (Transitive Dependency)
- X->Y이고, Y->Z이면, X->Z가 되는 경우
- 제3정규형을 통해 해결할 수 있다
- ex)
- 1번 함수 종속성 = '학번' -> '기숙사 ID'
- 2번 함수 종속성 = '기숙사 ID' -> '기숙사 이름'
: 따라서 '학번' -> '기숙사 이름'이 성립하기에 정규화가 필요하다
정규화(Normalization)
정규화
- 데이터를 구조화하여 중복을 최소화하고 데이터 무결성을 향상시키는 과정
- '논리 데이터 모델'의 일관성을 확보하고 중복을 제거한다. '개념 데이터 모델'이 아님!
- 데이터의 'CUD' 에서 발생할 수 있는 이상 현상을 방지하고, 데이터베이스의 효율성과 일관성을 높일 수 있음
- 하지만 정규화로 인해 '조회성능'이 저하될 수 있다. 이 경우 비정규화를 고려해볼 수 있다
주요 목적
- 데이터 중복 최소화
- 중복 데이터를 제거하여 저장 공간을 절약하고, 데이터 불일치 문제를 방지한다
- 데이터 무결성 유지
- 데이터의 일관성과 정확성을 보장하여 신뢰성을 높임
- 이상 현상 방지
- 삽입, 삭제, 갱신 시 발생할 수 있는 예기치 않은 문제를 예방
정규화 단계
1. 제1정규형 (1NF)
- 모든 필드가 '원자값(Atomic Value)'을 가지도록 구성된 테이블
- 즉, 각 컬럼이 하나의 값만을 가져야 하므로 list가 될 수 없다
- 왜 하는가?
- 컬럼 하나에 리스트로 여러 개를 들고있다면, 조회할 때 성능이 좋지 않음
- 'where 컬럼 = anything' 가 아니라, 'where 컬럼 = "%anything%"' 과 같이 조회해야함
- 또한 특정 컬럼으로 정렬하는 것이 불가능하기에, 제1정규화를 통해 해결할 수 있음
2. 제2정규형 (2NF)
- 제1정규형을 만족하면서, '부분 함수 종속성'을 제거한 테이블
- 즉, 기본 키의 일부에만 종속적인 속성을 분리하여 별도의 테이블로 구성해야한다
- 현재 테이블의 핵심 주제와 관련없는 컬럼을 다른 테이블로 빼는 작업을 '제2정규화'라 한다
- 왜 하는가?
- 데이터의 중복을 줄일 수 있음
- 아래 예시에서 '김동환'이 개명을 했다면, 모든 row의 '김동환'을 찾아서 하나씩 다 바꿔줘야함
- 하지만 제2정규화를 한다면, 단 한번의 수정만으로 해결할 수 있음
- 단점은 테이블 하나에서 모든 데이터를 다 조회할 수 없음
- ex)
[수강정보 - before]
학번 | 이름 | 수강 과목 | 교수명 |
1001 | 김동환 | 농구 | 데릭 로즈 |
1001 | 김동환 | UFC | 일리아 토푸리아 |
1002 | 농구잘함 | 데이터베이스 | GPT |
1003 | 인정 | 스프링부트 | 김영한 |
: 수강정보 테이블을 보면 주제와 관련없는 '이름' 컬럼이 존재한다.
또한 현재 테이블에서는 pk가 따로 존재하지 않고, '학번' + '수강 과목'을 합친 composite primary key를 pk로 사용할 수 있다.
이때 '이름' 컬럼은 '수강과목' 컬럼이 아닌 '학번' 컬럼에만 종속적이므로, 부분 함수 종속성(partial dependency)가 있다고 볼 수 있고, 이를 따로 분리하여 별도의 테이블로 만들 수 있다.
제2정규화 결과는 다음과 같다
[수강정보 - after]
학번 | 수강 과목 | 교수명 |
1001 | 농구 | 데릭 로즈 |
1001 | UFC | 일리아 토푸리아 |
1002 | 데이터베이스 | GPT |
1003 | 스프링부트 | 김영한 |
[학생]
학번 | 이름 |
1001 | 김동환 |
1002 | 농구잘함 |
1003 | 인정 |
: '학번' 컬럼에만 종속적이던 '이름' 컬럼을, 별도의 '학생' 테이블로 분리하여 부분 함수 종속을 없애주었다. 그 결과 각 테이블에서는 주제와 관련없는 컬럼이 더이상 존재하지 않기에, 제2정규형을 만족한다.
3. 제3정규형 (3NF)
- 제2정규형을 만족하면서, '이행 함수 종속성'을 제거
- 즉, '기본 키가 아닌 속성'들 간의 종속 관계를 분리하여 독립적인 테이블로 구성
- pk를 제외한 다른 컬럼에서는 종속 관계가 있으면 안된다
- ex)
[과목 정보 - before]
과목명(pk) | 강의실 | 교수명 | 출신 |
농구 | 넉터 | 데릭 로즈 | 미국 |
UFC | 옥타곤 | 일리아 토푸리아 | 조지아 |
데이터베이스 | Open AI | GPT | 미국 |
스프링부트 | 인프런 | 김영한 | 한국 |
: 과목 정보 테이블에서는 pk가 명확하게 '과목명' 컬럼으로 설정되어있다. 그런데 '출신' 컬럼을 보면, 이 컬럼은 '교수명' 컬럼에 종속적이란 것을 알 수 있다. 따라서 이행 함수 종속(transitive dependency)가 있다.
이제 이들을 분리해서 제3정규형을 만족시키면 다음과 같다.
[과목 정보 - after]
과목명(pk) | 강의실 | 교수명 |
농구 | 넉터 | 데릭 로즈 |
UFC | 옥타곤 | 일리아 토푸리아 |
데이터베이스 | Open AI | GPT |
스프링부트 | 인프런 | 김영한 |
[교수 정보]
교수명(pk) | 출신 |
데릭 로즈 | 미국 |
일리아 토푸리아 | 조지아 |
GPT | 미국 |
김영한 | 한국 |
: pk가 아닌 '교수명' 컬럼에 종속적이던 '출신' 컬럼을 따로 분리함으로써 제3정규형을 만족하게되었다.
4. 보이스-코드 정규형 (BCNF)
- 제3정규형을 강화한 형태로, 모든 결정자가 후보 키인 상태
- 즉, 후보 키가 아닌 결정자를 제거하여 테이블을 분리
반정규화 (Denormalization)
반정규화
- 이미 정규화를 거친 데이터베이스 설계에서, 성능(특히 조회) 개선이나 개발과 운영의 단순화를 위해 의도적으로 중복, 통합, 분리 등을 수행하는 기법
- 정규화로 인한 여러 테이블 간의 조인 비용이 부담스러울 때, 데이터를 한 테이블에 결합하거나 중복된 컬럼을 추가하여 조회 속도를 높일 수 있다
주요 목적
- 성능 향상
- 정규화된 데이터베이스는 조회를 위해 여러 테이블 간의 조인 연산이 빈번해질 수 있다
- 따라서 반정규화를 통해 조인 횟수를 줄여 조회 속도를 높일 수 있다
- 쿼리 단순화
- 하나의 테이블에 결합된 데이터들을 사용하여, 복잡한 쿼리를 단순화 시킬 수 있다
- 개발과 운영의 단순화
반정규화 절차
- 반정규화 대상 조사
- 자주 사용되는 테이블에 접근하는 프로세스의 수가 많고, 항상 일정한 범위만을 조회하는 경우
-> 필요한 부분만을 저장하는 테이블로 분리 - 테이블에 대량의 데이터가 있고, 그 데이터 범위를 자주 처리하는 경우
-> 데이터 범위별로 데이터를 분리 저장 - 통계성 프로세스에 의해 통계 정보를 필요로 할 때
-> 별도의 통계 테이블을 생성 - 테이블에 지나치게 많은 조인이 걸려 데이터를 조회하는 작업이 기술적으로 어려운 경우
-> 조인에 필요한 데이터들을 하나의 테이블로 합치거나, 필요한 데이터들의 중복을 허용하여 해결
- 자주 사용되는 테이블에 접근하는 프로세스의 수가 많고, 항상 일정한 범위만을 조회하는 경우
- 다른 방법 검토
- VIEW 테이블 사용
- 지나치게 많은 조인이 걸려 데이터를 조회하는 작업이 '기술적'으로 어려울 경우 view를 사용한다
- 이때 view 테이블을 사용함으로써 복잡한 쿼리를 미리 저장해두고 간단한 쿼리로 조회할 수 있다
- 따라서 기존의 쿼리를 그대로 사용하는 것이므로, 성능향상이 일어나는 것은 아니다
- 클러스터링
- 대량의 데이터처리나 특정 컬럼을 기준으로 정렬된 상태를 자주 검색하는 경우 클러스터링을 사용한다
- 테이블 내 데이터를 특정 컬럼을 기준으로 '물리적으로 가까이' 저장하는 기법이다
- 클러스터링을 사용하면 데이터를 미리 정렬된 상태로 저장해서 조회 속도를 향상시킬 수 있다
- 파티셔닝
- 대량의 데이터를 특정 기준으로 자주 조회하거나, 특정 범위의 데이터만 필요한 경우 사용한다
- 데이터를 특정 기준에 따라 여러 개의 테이블로 나누는 기법을 말한다
- 캐시
- 자주 조회하는 데이터인데, 데이터의 변경이 자주 일어나지 않는 경우 사용한다
- DB에 저장된 데이터를 캐시에 저장해두고, 데이터 조회가 발생했을 때 DB가 아닌 캐시에 접근해서 가져오는 기법을 말한다
- DB에 접근하기 전에 캐시에서 데이터를 가져오므로 DB 부하를 줄일 수 있고, 성능도 향상시킬 수 있다
- VIEW 테이블 사용
반정규화 기법
- 테이블 병합
- 조인을 줄이기 위해 정규화된 여러 테이블을 하나로 합치는 기법이다
- 1:1 관계, 1:M 관계, 슈퍼/서브 관계를 통합하여 성능을 향상시킬 수 있다
- 테이블 분할
- 수직분할 -> I/O 부하를 줄이기 위해 column을 기준으로 분할하는 방법
- 대상
- 컬럼이 많은 경우 사용한다 - 원인
- 한 테이블에 너무 많은 컬럼이 존재한다면, 각 row의 크기가 커질 가능성이 높음
- 따라서 로우 체이닝(row chaining)과 로우 마이그레이션(row migration)이 발생할 수 있고, 이로 인해 데이터가 여러 블록에 걸쳐 저장됨
- 이로 인해 디스크에서 데이터를 읽을 때, 연속적인 블록을 읽어서 끝내지 못하고 여러 블록을 따로따로 읽어야 하므로 성능 저하가 발생함
(* 로우 체이닝: 데이터 크기가 블록 용량을 초과하여 한 블록에 다 저장되지 못하고, 여러 블록에 나뉘어 저장되는 현상)
(* 로우 마이그레이션: 한 블록에 저장된 데이터가 수정되어 크기가 커지면서, 기존 블록에 다 들어가지 못하여 여러 블록에 걸쳐서 저장되는 현상) - 방법
- 자주 조회하는 컬럼끼리 그룹을 나눠서 여러 테이블로 분할한다
- 대상
- 수평 분할 -> Index 부하를 줄이기 위해 row를 기준으로 분할하는 방법
- 대상
- 데이터가 많은 경우 사용한다 - 원인
- 한 테이블에 너무 많은 데이터가 저장되어있으면, 특정 조건을 만족하는 데이터를 찾기 위한 검색 범위가 넓어짐
- 따라서 인덱스(PK 등) 크기가 커지고, 데이터 조회시 스캔해야 할 데이터 양이 증가하여 성능이 저하됨 - 방법
- 데이터를 범위(Range), 해시(Hash), 리스트(List) 등 특정 기준으로 나누어서 저장하는 파티셔닝을 사용
- Range Partitioning: 날짜, 가격, 연도 등의 '범위'를 기준으로 데이터를 분할
- Hash Partitioning: 특정 키 값을 해싱하여 데이터를 나누는 방식 (ex. id나 고객번호 등에 해시 함수, 예를들면 대상값%3의 결과에 따라 분산 저장)
- List Partitioning: 특정 값 목록에 따라 데이터를 분할 (ex. '지역' 기준으로 서울, 부산, 대전 테이블로 분리)
- 대상
- 수직분할 -> I/O 부하를 줄이기 위해 column을 기준으로 분할하는 방법