// 에드센스

디스크 읽기 방식

  • 데이터베이스 성능 튜닝은 디스크 IO를 어떻게 줄이느냐가 관건
  • 데이터베이스에서 병목지점은 항상 디스크 장치
    • 하드디스크
    • SSD → 하드디스크와 비교해서 용량이 적으며 비싸지만 더 사용된다 왜? 랜덤IO가 빨라서

 

순차IO와 랜덤IO

  • 디스크 헤더가 안움직이고 한번에 읽는다 → 순차IO
  • 헤더 이동없이 얼마나 많이 읽을 수 있는가 → 디스크의 성능
  • 순차IO가 랜덤IO보다 항상 성능이 좋다 (약 3배정도)(SSD의 경우도 그렇다)
  • so, 쿼리튜닝 == 랜덤IO를 줄인다

 

인덱스란?

  • DBMS가 데이터베이스 테이블의 모든 데이터를 검색해 원하는 결과를 가져오려면 시간이 오래 걸린다.
  • 그래서 컬럼들의 값과 해당 레코드가 저장된 주소를 키와 값의 쌍으로 삼아 인덱스를 만들어두는 것
  • DBMS의 인덱스는 컬럼의 값을 주어진 순서로 미리 정렬해서 보관한다.
  • 키밸류로 컬럼값과 레코드가 저장된 주소를 미리 저장해두는 자료구조
  • INSERT, UPDATE, DELETE 성능을 포기하고 읽기 속도에 몰빵하는 기능

 

인덱스 알고리즘

  • 비트리 알고리즘
  • 해쉬 인덱스 알고리즘

 

B-Tree 인덱스

  • 칼럼의 원래 값을 변형하지 않고 인덱스 구조체 내에서는 항상 정렬된 상태로 유지한다
  • 데이터베이스에서 인덱스와 실제 데이터가 저장된 데이터는 따로 관리되는데,
  • 인덱스의 리프 노드는 항상 실제 데이터 레코드를 찾아가기 위한 주솟값을 가지고 있다.

 

B-Tree는 왜 빠른가?

  • 어떠한 값에 대해서도 같은 시간에 결과를 얻을 수 있다
  • 리프노드를 선형 탐색한다면 71이라는 값을 찾기위해 O(n)
  • B-tree는 O(logN)

 

B+Tree

  • 리프노드끼리 LinkedList형태로 연결됨
  • 하나의 노드에 더 많은 데이터를 담기에 트리의 높이가 낮아짐
  • 리프노드에 모든 데이터가 있기에 풀스캔시 리프노드의 LinkedList를 한번만 선형탐색하면 된다

 

 

B-Tree 인덱스 사용에 영향을 미치는 요소

 

인덱스 키 값의 크기

  • 인덱스도 페이지 단위로 관리됨
  • 인덱스를 구성하는 키값의 크기가 커지면 디스크로부터 페이지를 읽는 횟수가 늘어난다 → 느려진다
    • 이노디비의 기본 페이지 크기는 16KB
    • 추가

 

비트리의 깊이

  • 깊이도 중요하지만 제어할 수 없다
  • 인덱스 키 값이 커지면 하나의 인덱스 페이지에 더 적은 수의 키 값이 저장되고, 비트리의 깊이가 깊어짐 → 더 많은 디스크 읽기

 

인덱스의 선택도(기수성)

  • 선택도(Selectivity) or 기수성(Cardinality)
  • 모든 인덱스 값 중에 유니크한 값의 수
    • 100개중에 유니크한게 10개라면 기수성은 10
  • 선택도가 높다면 검색 대상이 줄어들기에 빠르다
SELECT * FROM tb_city
WHERE country='KOREA' AND city='SEOUL';
  • 다음과 같은 조건
    • tb_city 테이블에는 1만건의 데이터
    • country 컬럼에만 인덱스
    • country와 city 컬럼은 중복없다
  • country 컬럼의 유니크 값이 10개일 때
    • country=’KOREA’ 라는 조건으로 인덱스를 검색하면 1만 건의 데이터 중 1,000건(10,000 / 10)의 데이터가 일치할 것이라 예상할 수 있다.
    • city=’SEOUL’인 레코드는 1건이므로 1,000건 중 999건이 불필요하게 읽히는 것으로 볼 수 있다.
  • country 컬럼의 유니크 값이 1,000개일 때
    • country=’KOREA’ 라는 조건으로 인덱스를 검색하면 1만 건의 데이터 중 10건(10,000 / 1,000)의 데이터가 일치할 것이라 예상할 수 있다.
    • city=’SEOUL’인 레코드는 1건이므로 10건 중 9건이 불필요하게 읽히는 것으로 볼 수 있다.

 

읽어야하는 레코드의 수

  • 인덱스로 읽어야하는 데이터가 전체 테이블 레코드의 20~25%를 넘어가면 보통 인덱스를 안쓰고 풀스캔
    • 예시로, 100만건중에 50만건을 읽는 쿼리가 있다면 인덱스 사용안하고 풀스캔

 

 

MySQL의 인덱스 사용방법

  • 인덱스 레인지 스캔
  • 인덱스 풀 스캔
  • 루스 인덱스 스캔 (Loose)
  • 인덱스 스킵 스캔

 

인덱스 레인지 스캔

  • 가장 대표적인 방식
  • 검색해야할 인덱스 범위가 결정됐을때 사용
  • 인덱스의 리프노드에서 검색 조건에 일치하는 건들은 데이터 파일에서 레코드를 읽어오는 과정이 필요하다
SELECT * FROM employees WHERE first.name BETWEEN 'Ebbe' AND 'Gad';

  • 두꺼운 화살표가 실제 스캔하는 범위이다
  • 루트노드 → 브랜치노드 → 리프노드를 거쳐 필요한 레코드의 시작점을 찾는다
  • 리프노드에서 Ebbe와 Gad를 찾았다면 그 사이를 스캔한다
  • 그 사이에 대해서는 실제 데이터 파일로 랜덤엑세스를 통해 가져오는 과정이 필요하다
  • 만약 커버링 인덱스가 적용됐다면 이 과정 없음
    • 쿼리를 충족시키는 데 필요한 모든 데이터를 갖고 있는 인덱스를 커버링 인덱스라고한다

커버링 인덱스

  • customer_id에만 인덱스가 있다고 했을때
select *
from temp_ad_offset
where customer_id = 7;

select customer_id
from temp_ad_offset
where customer_id = 7;

Extra 항목은 옵티마이저가 어떻게 동작하는지에 대해 알려주는 힌트 값

  • Using filesort: Orderby처리가 인덱스를 사용하지 못할때
  • Using index: 커버링 인덱스 사용할때
  • Using temporary: 쿼리의 중간결과를 위해 임시테이블을 생성했을때
  • Using index for skip scan: 인덱스 스킵 스캔 최적화를 한 경우

https://zzang9ha.tistory.com/436

 

MySQL EXPLAIN 실행계획 마스터하기(feat. RealMySQL 8.0)

💯 MySQL EXPLAIN 실행계획 마스터하기(feat. RealMySQL 8.0) 실행 계획(EXPLAIN) 이란? 대부분의 DBMS는 많은 데이터를 안전하고, 빠르게 저장 및 관리하는 것이 주목적이다. 이러한 목적을 달성하기 위해 사

zzang9ha.tistory.com

 

 

 

인덱스 풀 스캔

  • 인덱스의 처음부터 끝까지 모두 읽는 방식
  • 인덱스 전체 크기는 테이블의 전체크기보단 작기에 테이블 풀스캔보단 나은 비용
  • (A, B, C) 라는 인덱스가 있다
SELECT * FROM employees WHERE B = 'b' AND C = 'c';
  • 조건절에 사용된 컬럼이 A를 포함하지 않기에 풀스캔 발생

 

루스 인덱스 스캔

  • 듬성듬성 인덱스를 읽는 방식
  • 인덱스 레인지 스캔과 비슷하지만, 중간에 필요하지 않은 인덱스 키 값은 무시한다
  • 일반적으로 groupby나 max(), min() 함수에 대한 최적화로서 옵티마이저가 판단한다

 

인덱스 스킵 스캔

  • MySQL8.0부터 추가된 기능
  • 조건절에 첫번째 인덱스가 없어도 두번째 인덱스만으로 인덱스 사용하게해주는 기능
  • 바로 위 인덱스 풀 스캔의 쿼리가 인덱스를 타게된다
  • 단,
    • where절에 포함되지않은 인덱스 컬럼의 유니크한 개수가 적어야함(A컬럼의 선택도가 낮아야함)
    • 그렇지 않다면 인덱스에서 스캔해야 할 시작지점을 검색하는 작업이 많아지며 오히려 더 느려진다
    • 커버링 인덱스를 사용할 수 있는 경우에만 스킵스캔이 된다.

 

 

멀티밸류 인덱스

  • 다중컬럼 인덱스(복합인덱스)
  • 각 컬럼은 자기 바로 앞 컬럼에 의존하여 정렬돼있음
    • 두번째 컬럼은 첫번째 컬럼에 의존해 정렬돼있다
    • 세번째 컬럼은 두번째 컬럼에 의존해 정렬돼있다
  • 선택도가 높은 컬럼을 앞쪽으로
    • where절에서 자주 사용되는 조건을 앞쪽으로
    • join조건에서 자주 사용되는 조건을 앞쪽으로
    • orderby절에서 자주 사용되는 조건을 앞쪽으로

 

 

인덱스 스캔 방향

  • 인덱스를 생성할 때 설정한 규칙에 따라서 오름차순/내림차순으로 정렬되어 저장된다
  • 오름차순으로 저장되었다고 해서 오름차순으로만 이용할 수 있는건 아니다
    • 오름차순 인덱스를 거꾸로 읽으면 내림차순으로 정렬된 인덱스로도 활용 가능
    • 실행계획에서 결정됨
    • ORDER BY 처리나 MIN() 또는 MAX() 함수 등의 최적화가 필요한 경우 인덱스를 읽는 순서만 변경해서 인덱스 생성 시 지정한 정렬 규칙에 대한 문제점을 해결할 수 있다.

 

내림차순 인덱스는 느리다

  • 왜?
    • 페이지 잠금이 인덱스 정순 스캔에 적합한 구조이므로
    • 페이지 내에서 인덱스 레코드가 단방향으로만 연결된 구조이므로
  • 쿼리에서 자주 사용되는 정렬 순서대로 인덱스를 생성하는 것이 잠금 병목 현상을 줄이는 데 도움됨

 

 

B-tree 인덱스의 가용성과 효율성

  • where조건이나 groupby, orderby절이 어떤 경우에 인덱스를 사용하는지 알아야함

 

비교 조건의 종류와 효율성

SELECT * FROM dept_emp
WHERE dept_no= 'd002' AND emp_no >= 10114 ;
  • 1번: 인덱스가 (dept_no, emp_no)인 경우
  • 2번: 인덱스가 (emp_no, dept_no)인 경우

1번 케이스

  • dept_no가 ‘d002’이고 emp_no가 10114보다 큰 레코드를 찾는다.
  • 이후에는 dept_no가 ‘d002’가 아닐 때까지 인덱스를 쭉 읽기만 하면 된다.

2번 케이스

  • emp_no가 10114 보다 큰 레코드이고 dept_no가 ‘d002’인 레코드를 찾는다.
  • 이후 찾은 모든 레코드에 dept_no가 ‘d002’인지 비교하는 작업을 수행한다.

 

 

인덱스의 가용성

  • B-Tree 인덱스의 특징은 왼쪽 값에 기준해서 오른쪽 값이 정렬돼 있다는 것이다.
  • 하나의 칼럼으로 검색해도 값의 왼쪽 부분이 없으면 인덱스 레인지 스캔 방식의 검색이 불가능하다.
SELECT * FROM employees WHERE first_name LIKE '%mer1';
  • 이 쿼리는 인덱스를 못탄다
    • first_name 컬럼에 저장된 값의 왼쪽부터 비교해가며 일치하는 레코드를 찾아야 하는데,
    • 조건 값의 왼쪽 부분(’%mer1’)이 정해져 있지 않기 때문이다
SELECT * FROM dept_emp WHERE emp_no>=10144;
  • 인덱스가 (dep_no, emp_no)로 생성되어 있다면 아래의 쿼리는 인덱스를 효율적으로 사용하지 못한다
  • 다중 컬럼 인덱스로 생성된 인덱스이므로 dept_no를 먼저 정렬한 후, 다시 emp_no 컬럼값으로 정렬돼 있기 때문 (랜덤IO)

 

B-tree에서 인덱스를 효율적으로 사용하지 못하는 케이스

  • NOT-EQUAL로 비교된 경우 (NOT IN, NOT BETWEEN, IS NOT NULL)
    • WHERE column <> 'N'
    • WHERE column NOT IN (10,11,12)
    • WHERE column IS NOT NULL
  • LIKE %xxx 형태 문자열 패턴 비교인 경우
    • WHERE column LIKE '%test‘
    • WHERE column LIKE '%test%‘
    • WHERE column LIKE '_test‘
  • 스토어드 함수나 다른 연산자로 인덱스 컬럼이 변형된 후 비교된 경우
    • WHERE SUBSTRING(column,1, 1) = 'X'
    • WHERE DAYOFMONTH(column) = 1
  • 인덱스 컬럼의 타입을 변환해야 비교가 가능한 경우
    • WHERE char_column = 10 → char를 int와 비교
  • 문자열 데이터 타입의 콜레이션이 다른 경우
    • WHERE utf8_bin_char_column = euckr_bin_char_column

 

 

클러스터링 인덱스

  • pk가 비슷한 레코드끼리 묶어서 인덱스로 저장한 것
  • 클러스터링 인덱스의 리프노드에는 모든 레코드의 컬럼이 저장되어있다
  • 클러스터링이란
    • 여러개를 하나로 묶는다는 의미로 주로 사용됨
    • MySQL 서버에서 클러스터링은 테이블의 레코드를 비슷한 것을 기준으로 묶어서 저장하는 형태
    • 비슷한 것 = pk
    • pk설정시 그 컬럼은 자동으로 클러스터링 인덱스가 된다
    • 주로 비슷한 값을 동시에 조회하는 경우가 많다는 점에서 착안된 설계
    • 테이블의 레코드가 pk 기준으로 정렬되어 저장되는 경우를 → 클러스터링 인덱스 or 클러스터링 테이블
  • pk에 따라 레코드의 저장위치가 결정되므로 테이블의 레코드 저장 방식으로 볼 수 있다
    • 인덱스 자체의 리프노드가 곧 데이터다. → 테이블 자체가 인덱스다
      • 클러스터 인덱스는 페이지를 알기 때문에 바로 그 페이지를 펴는 것
      • 넌 클러스터 인덱스는 뒤에 목차에서 찾고자 하는 내용의 페이지를 찾고 그 페이지로 이동하는 것.
      • 테이블 스캔은 처음부터 한 장씩 넘기면서 내용을 찾는 것
  • pk가 없다면 다음 기준으로 이노디비 스토리지 엔진이 pk를 대체할 컬럼을 선정한다
    • pk가 있다면 pk를 클러스터링 키로 선택
    • not null인 유니크 인덱스중에 첫번째를 클러스터링 키로 선택
    • 자동으로 유니크한 값을 가지도록 증가되는 컬럼을 내부적으로 고른 후 클러스터링 키로 선택
  • 장점 (주로 빠른 읽기)
    • pk(클러스터링키)로 검색할때 성능이 매우 빠름(특히 pk를 범위검색에 사용하는 경우)
    • 테이블의 모든 세컨더리 인덱스가 pk를 가지고있기 때문에 인덱스만으로 쿼리를 처리할 수 있을 확률이 높음(커버링 인덱스)
  • 단점 (주로 느린 쓰기)
    • 테이블의 모든 세컨더리 인덱스가 pk를 가지고있기 때문에 클러스터링 키값이 클수록 전체적으로 인덱스의 크기가 커짐
    • 세컨더리 인덱스로 검색할때 pk로 한번 다시 검색해야하기에 처리성능이 느림
    • INSERT를 할 때 pk에 의해 레코드 저장 위치가 결정되기에 처리 성능이 느림
    • pk를 변경할 때 레코드를 DELETE하고 INSERT해야하기에 처리성능이 느림

  • 클러스터링 인덱스는 인덱스에 실제 데이터를 저장하고있음
  • 논 클러스터링(세컨더리) 인덱스는 인덱스 페이지를 별도로 관리함
    • 논 클러스터링 인덱스의 장단점
      • 장점
        • 실제 데이터 페이지는 정렬되지 않기에 INSERT, UPDATE, DELETE 성능 좋음
      • 단점
        • 인덱스만 정렬되고 실제 데이터는 정렬되지 않기에 클러스터링 인덱스에 비해 검색 느림

 

논 클러스터링 인덱스(세컨더리 인덱스)

  • 클러스터링 인덱스(PK 인덱스)를 제외한 모든 인덱스
  • 리프 노드에서 레코드의 물리적인 주소값이 아닌 PK를 가지기 때문에 레코드를 접근할 때 바로 접근할 수 없다
  • 왜 논 클러스터링 인덱스를 통한 검색은 클러스터링 인덱스를 한번 거치게 했을까?
    • 레코드 변경시 인덱스의 부하를 줄이기 위함
    • pk가 변경되면 레코드의 물리적 주소가 바뀜
    • 논 클러스터링 인덱스는 실제 레코드 주소값(pk)이 아닌 논리적인 pk만 참조하기에 레코드의 값이 변경되어도 논 클러스터링 인덱스는 바뀌지 않는다

클러스터링 인덱스 → pk(데이터)

논 클러스터링 인덱스 → 클러스터링 인덱스 → pk(데이터)

 

  • 클러스터링 인덱스: 인덱스를 가지고 리프노드까지 탐색해서 원하는 데이터를 바로 얻음
  • 논 클러스터링 인덱스: 인덱스를 가지고 리프노드에 탐색해서 실제 데이터 위치를 얻음 → 실제 데이터는 힙영역에서 얻는다

 

클러스터링 테이블 사용시 주의사항

  • 이노디비 테이블(클러스터링 테이블)에서는 주의할 것이 있다
  • 모든 논 클러스터링(세컨더리) 인덱스가 pk를 포함한다
  • 그래서 pk의 크기가 커지면 세컨더리 인덱스도 커진다
  • 그래서 다음과 같은 주의사항들이 있다
    • pk는 auto_increment 보다는 업무적인 컬럼으로 생성
      • 이노디비에서 pk는 클러스터링 키로 사용되며, 이 값에 따라서 레코드의 위치가 결정됨
      • 즉, pk로 검색하는 경우 클러스터링 되지 않은 테이블에 비해서 매우 빠르다는 뜻
      • 따라서 검색에서 빈번하게 사용되고 → 업무적으로 pk가 해당 레코드를 대표할 수 있다면 pk로 설정하는 것이 좋다
    • pk는 반드시 명시할 것
      • 명시하지 않으면 이노디비 스토리지 엔진이 내부적으로 일련번호 컬럼을 추가한다
      • 하지만 이 컬럼은 보이지 않기에 사용자가 접근할 수 없다
      • 따라서 명시하지 않아도 일련번호 컬럼이 추가되기에 명시하는거나 다름없다(오히려 접근이 안되기에 사용불가라는 단점만 있음)
    • auto_increment 컬럼을 인조 식별자로 사용할 경우
      • 세컨더리 인덱스도 필요하고 프라이머리 키의 크기도 길다면 AUTO-INCREMENT 칼럼을 추가하고 이를 프라이머리 키로 설정하면 된다.
      • 이를 인조 식별자라고 함

 

 

유니크 인덱스

  • 같은 값이 2개 이상 저장될 수 없음을 의미
  • 인덱스라기보단 제약조건에 가깝다 → 하지만 인덱스 없이 유니크 제약만 설정할 방법이 없다
  • 유니크 인덱스가 걸린 컬럼에 null도 저장될 수 있지만 null은 중복 허용임
  • 유니크 인덱스가 다른 세컨더리 인덱스보다 조회가 빠른가?
    • 그렇긴한데 유니크”인덱스”라서 빠른건 아니다
    • 인덱스의 성격이 유니크한지 아닌지의 차이이지 인덱스 자체가 동작하는 방식은 동일하다
      • 유니크하지 않은 세컨더리 인덱스는 읽어야 할 값이 많아서 느린것
      • 유니크 인덱스는 1개뿐이라서 빠른것 (1개뿐임이 보장되기에 실행계획이 다름)
      • 인덱스 자체에 대한 차이는 아님
  • 주의
    • 유니크 인덱스는 세컨더리 인덱스보다 변경에 느림
    • 일반인덱스와 유니크인덱스는 인덱스 자체로는 동일하다
    • pk를 유니크 인덱스로 쓰지말자 중복이다

 

 

참고:

 

'DB' 카테고리의 다른 글

[Typeorm] save() 알차게 사용하기  (1) 2023.04.01
[DB] 데이터베이스 정규화  (0) 2021.07.14

 

Typeorm 문서에는 save() 메소드를 이렇게 설명한다

  • 엔티티가 이미 존재하면 update
  • 엔티티가 존재하지 않으면 insert

 

엔티티가 이미 존재하는지 어떻게 알까?

그렇다.

save() 메소드를 호출하면 select쿼리를 한번 실행시키고 그 뒤에 insert나 update 쿼리를 실행시킨다.

 

즉, 쿼리가 두번 나간다.

 

 

 

요런 코드를 돌려보자

[1]에서 콘텐츠 엔티티를 저장하고

[2]에서 저장한 엔티티를 조회하여 수정하고

[3]에서 수정한 엔티티를 다시 저장해보자

 

 

 

쿼리는 어떻게 발생할까?

[1]에서는 기존에 없던 저장돼 있지 않던 엔티티이기에 select 쿼리 이후 insert쿼리가 발생한 것을 볼 수 있다.

[3]에서는 해당 엔티티의 pk기준으로 이미 존재하는 엔티티이기에 select 쿼리이후 update 쿼리가 발생한 것을 볼 수 있다.

 

 

 

근데 이제 select 쿼리는 계속 발생한다는 말인데 만약 저장하거나 수정해야 할 데이터가 많다면 각 쿼리마다 select 쿼리가 발생할텐데 상당히 비효율적이 아닐수가 없다.

 

 

 

직접 update() 메소드나 insert() 메소드를 사용하면 이런 문제를 개선할 수 있다.

 

 

얌전하게 하나의 쿼리만 발생했다.

 

 

 

물론 save() 메소드가 주는 편리함을 포기하고 사용하는 것이므로 update나 insert를 할 때 디비에 중복되는 데이터가 이미 존재하는지 확인된 상태에서 사용하는것을 권한다. save()는 그냥 무지성으로 사용해도 pk가 겹치는게 아닌 이상 의도한대로 동작은 했는데 update(), insert()는 아니다.

 

 

'DB' 카테고리의 다른 글

[Real MySQL] 인덱스  (1) 2024.10.31
[DB] 데이터베이스 정규화  (0) 2021.07.14

정처기에서 정규화 문제 틀리고 나서 쓰는 정규화 포스팅. 억울하지만 억울할거 없다. 크윽..

 

 

 

데이터베이스 정규화란?

이상 문제를 해결하기 위해 속성들끼리의 종속관계를 분석하여 여러 릴레이션으로 분해하는 과정.

테이블을 여러개로 분리하는 과정이다보니 속도는 상대적으로 느려질지라도 이상문제들을 방지할 수 있다.

 

 

이상 문제

삽삭생

  • 삽입 이상: 데이터를 저장할 때 원치않는 정보가 함께 삽입되는 경우
  • 삭제 이상: 튜플을 삭제하면서 유지돼야하는 정보까지도 연쇄적으로 삭제되는 경우
  • 갱신 이상: 튜플 중 일부 속성만 갱신시킴으로써 정보의 모순성이 발생하는 경우

 

다음의 테이블에서 예를 들어보자.

 

 

  • 삽입 이상 : 신입 학생이 입학하여 학번과 학년 등을 입력하려 했으나 아직 과목이 정해지지 않았거나, 시험을 보지 않아 성적이 없는 상태이기 때문에 불필요한 정보(과목 이름, 성적)를 함께 삽입 해야 함
  • 삭제 이상 : 학생 번호가 2번인 학생의 과목에 대한 성적을 삭제할 경우 학생 번호와 학년 등 모든 정보가 같이 삭제되어 학생의 정보 자체가 사라짐
  • 갱신 이상 : 학생 번호가 3번인 학생이 2학년이 되어 학년 정보를 변경 하려 하는데 3개를 모두 하나씩 바꿔줘야 함, 하나라도 안바꿀 경우 한명의 학생에 대한 정보가 서로 달라지는 정보의 모순성이 발생

 

 

 

정규화 과정

원부이 결다조

제 1 정규화

도메인은 전부 원자값이어야한다. 원자값이란 유일한 값. 

이 테이블의 핸드폰번호 컬럼은 원자값을 가지고있지 않다. 제 1정규화를 하면 다음과 같은 모습을 가진다.

 

 

 

 

제 2 정규화

2정규화는 부분 함수 종속을 제거한다. 종속성이란 X -> Y 처럼 표현할 수 있다. 이때 X는 결정자, Y는 종속자라고 한다. X가 Y를 결정하고, Y는 X에 종속돼 있다는 의미이다. 즉, 학번을 알면 이름을 알 수있다고 하면 학번이 결정자, 이름이 종속자가 된다.

 

다음 테이블을 보자.

함수(학번, 과목코드)에서 부분 함수인 학번 혼자서 학부와 등록금을 결정할 수 있기 때문에 제2 정규형을 만족할 수 없다. 그러므로 성적, 학부, 등록금에 모두 영향을 주는 학번을 기준으로 릴레이션을 아래와 같이 분리 시킵니다.

 

 

 

 

제 3 정규화

제 3정규화는 이행 함수 종속을 제거한다. 

이 테이블은 학번(X)이 학부(Y)를 결정하고 학부(Y)가 등록음(Z)을 결정하기에 학번(X)이 등록금(Z)을 결정한다고 할 수 있다. 이는 제 3정규형을 만족시키지 못하며 이행 함수 종속을 제거해주어야 한다.

(이행 규칙: X -> Y 이고 Y -> Z면 X -> Z다. 이러면 안됨)

이렇게 분해를 해주면 3정규화가 완료된다.

 

 

그 뒤로는 BCNF(결정자 중 후보키가 아닌것 제거), 4정규화(다치 종속 제거), 5정규화(조인 종속 제거)가 있다만 3정규형까지만 수행해도 충분히 괜찮은 구조라고 한다! 나중에 BCNF 이후로의 필요성이 느껴진다면 다시 포스팅 해보도록 하겠다.

 

 

 

참고:

'DB' 카테고리의 다른 글

[Real MySQL] 인덱스  (1) 2024.10.31
[Typeorm] save() 알차게 사용하기  (1) 2023.04.01

+ Recent posts