DataBase

[SQLD] 함수 종속성, 정규화, 반정규화

DH_0518 2025. 2. 11. 01:36

 

 

 

 

함수 종속성 (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)

 

 

 

반정규화

  • 이미 정규화를 거친 데이터베이스 설계에서, 성능(특히 조회) 개선이나 개발과 운영의 단순화를 위해 의도적으로 중복, 통합, 분리 등을 수행하는 기법
  • 정규화로 인한 여러 테이블 간의 조인 비용이 부담스러울 때, 데이터를 한 테이블에 결합하거나 중복된 컬럼을 추가하여 조회 속도를 높일 수 있다

 

 

 

주요 목적

  • 성능 향상
    • 정규화된 데이터베이스는 조회를 위해 여러 테이블 간의 조인 연산이 빈번해질 수 있다
    • 따라서 반정규화를 통해 조인 횟수를 줄여 조회 속도를 높일 수 있다
  • 쿼리 단순화
    • 하나의 테이블에 결합된 데이터들을 사용하여, 복잡한 쿼리를 단순화 시킬 수 있다
  • 개발과 운영의 단순화

 

 

 

반정규화 절차

  1. 반정규화 대상 조사
    • 자주 사용되는 테이블에 접근하는 프로세스의 수가 많고, 항상 일정한 범위만을 조회하는 경우
      -> 필요한 부분만을 저장하는 테이블로 분리
    • 테이블에 대량의 데이터가 있고, 그 데이터 범위를 자주 처리하는 경우
      -> 데이터 범위별로 데이터를 분리 저장
    • 통계성 프로세스에 의해 통계 정보를 필요로 할 때
      -> 별도의 통계 테이블을 생성
    • 테이블에 지나치게 많은 조인이 걸려 데이터를 조회하는 작업이 기술적으로 어려운 경우
      -> 조인에 필요한 데이터들을 하나의 테이블로 합치거나, 필요한 데이터들의 중복을 허용하여 해결
  2. 다른 방법 검토
    • VIEW 테이블 사용
      • 지나치게 많은 조인이 걸려 데이터를 조회하는 작업이 '기술적'으로 어려울 경우 view를 사용한다
      • 이때 view 테이블을 사용함으로써 복잡한 쿼리를 미리 저장해두고 간단한 쿼리로 조회할 수 있다
      • 따라서 기존의 쿼리를 그대로 사용하는 것이므로, 성능향상이 일어나는 것은 아니다
    • 클러스터링
      • 대량의 데이터처리나 특정 컬럼을 기준으로 정렬된 상태를 자주 검색하는 경우 클러스터링을 사용한다
      • 테이블 내 데이터를 특정 컬럼을 기준으로 '물리적으로 가까이' 저장하는 기법이다
      • 클러스터링을 사용하면 데이터를 미리 정렬된 상태로 저장해서 조회 속도를 향상시킬 수 있다
    • 파티셔닝
      • 대량의 데이터를 특정 기준으로 자주 조회하거나, 특정 범위의 데이터만 필요한 경우 사용한다
      • 데이터를 특정 기준에 따라 여러 개의 테이블로 나누는 기법을 말한다
    • 캐시
      • 자주 조회하는 데이터인데, 데이터의 변경이 자주 일어나지 않는 경우 사용한다
      • DB에 저장된 데이터를 캐시에 저장해두고, 데이터 조회가 발생했을 때 DB가 아닌 캐시에 접근해서 가져오는 기법을 말한다
      • DB에 접근하기 전에 캐시에서 데이터를 가져오므로 DB 부하를 줄일 수 있고, 성능도 향상시킬 수 있다

 

 

 

반정규화 기법

  • 테이블 병합
    • 조인을 줄이기 위해 정규화된 여러 테이블을 하나로 합치는 기법이다
    • 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. '지역' 기준으로 서울, 부산, 대전 테이블로 분리)