데이터베이스 쿼리의 성능을 언급하면서 빼놓을 수 없는 부분이 인덱스 입니다. 이번에는 MySql에서 사용 가능한 인덱스의 종류 및 특성에 대해 살펴보도록 하겠습니다.
데이터베이스에서 어떠한 한 조건에 해당하는 데이터를 가져올 때 모든 테이블에 있는 데이터를 스캔하게 되면 엄청 많은 시간이 걸리게 됩니다. 그래서 이러한 값과 레코드가 저장된 주소를 Key Value 쌍으로 삼아 인덱스를 만들어 둡니다.
결국 CUD의 성능을 희생하고 데이터 읽기 속도를 높이는 기능을 합니다. 그렇기 떄문에 쓰기 및 수정 삭제 작업은 적고 읽기작업이 많은 테이블에 생성하게 되면 많은 이점을 얻을 수 있습니다. 다만 인덱스를 생성하면 생성한 만큼 저장공간을 차지하기 때문에 트레이드오프가 중요합니다.
B-Tree 인덱스
Hash 인덱스
Fractal-Tree 인덱스
출처 : https://gywn.net/2014/05/fractal-index-in-tokudb/
LSM(Log-Structured Merged-Tree) 인덱스
일반적인 트리 자료구조랑 같아서 최상위에는 루트노드가 존재하고 그 중간에 브랜치 노드들이 존재하고 가장 밑은 리프노드가 존재합니다. 리프노드에는 데이터 레코드를 찾아가기 위한 주소값(InnoDB는 PK)을 가지고 있습니다. 이때 트리의 노드를 살펴보면 인덱스키는 정렬이 되어있지만 데이터 파일의 레코드는 정렬되어있지 않습니다.(InnoDB는 클러스터되어 저장되기 떄문에 PK 순서로 정렬됩니다.)
B-Tree에 저장될 때는 저장될 키 값을 이용해 B-Tree상의 적절한 위치를 검색하고 저장될 위치가 결정되면 레코드의 키 값과 대상 레코드의 주소 정보를 리프 노드에 저장합니다. 하지만 이때 리프 노드가 꽉 찬 경우 리프 노드가 분리돼야 하는데 상위 노드까지 처리가 되어야 할 수도 있기 때문에 쓰기 작업은 성능이 별로 좋지 않습니다. InnoDB에서는 체인지 버퍼를 통해 작업을 지연 처리 시킬 수 있습니다.
B-Tree의 키 값이 삭제되면 B-Tree의 리프 노드를 찾아서 삭제 마크만 하면 작업이 완료 됩니다. 마킹작업은 DISK I/O가 필요하고 이렇게 마킹된 인덱스는 그대로 방치되거나 재활용할 수 있습니다.
B-Tree의 키 값을 변경하는 작업은 단순하게 값을 바꾸는 방식으로 동작하지 않습니다. 키값을 통해 리프 노드의 위치가 결정되기 떄문에 먼저 해당 키값를 삭제 한 뒤에 변경된 키값을 추가하는 방식으로 동작합니다. InnoDB에서는 체인지버퍼를 통해 작업을 지연 처리 시킬 수 있습니다.
B-Tree Index는 CUD 작업을 할 때 추가 비용을 감수하면서 인덱스를 구축하는 이유는 빠른 검색을 위해서 입니다. B-Tree Index를 이용한 검색은 100프로 일치하거나 값의 앞부분만 일치하는 경우에 사용할 수 있기 때문에 비교 조건에서도 인덱스를 활용할 수 있지만 인덱스를 구성하는 키 값의 뒷부분을 검색하는 용도로는 사용할 수 없습니다. 또한 변형된 키 값을 통해 비교 연산 하는 경우에는 Index에 포함된 값이 아니기 때문에 B-Tree의 이점을 살릴 수 없습니다.
💡 Index 키 값이 유니크 할 수록 검색 대상이 줄어들기 때문에 그만큼 빠르게 처리된다.항상 Index를 통해 레코드를 읽는다고 빠르지 않습니다. 어떤 경우에는 Full Scan을 하는 경우가 더 빠를 수 있습니다. 100만 건의 데이터가 저장 되어있을 때 50만 건을 읽어야 하는 쿼리가 있다고 가정했을 때 Full Scan을 통해서 100만 건을 읽어 50만 건을 버릴지 인덱스를 통해 50만 건을 읽는게 효율적인지 판단해야 합니다. 인덱스를 통해 읽어야 할 레코드의 건수가 전체 테이블 레코드의 20~25퍼센트를 넘어서면 인덱스를 이용하지 않는게 효율적 입니다.
Index
ALTER TABLE employees ADD INDEX ix_gender_birthdate (gender, birth_date);
1.
SELECT * FROM employees WHERE birth_date>='1965-02-01';
2.
SELECT * FROM employees WHERE gender ='M' AND birth_date>='1965-02-01';
위와 같은 Index를 생성했을 때 1번 쿼리 같은 경우는 Index를 활용하지 못합니다 하지만 2번 쿼리 같은 경우는 index에 두 컬럼 모두 포함되어있기 때문에 index를 활용해서 검색을 합니다. MySql 8.0 버전 부터는 1번 같은 쿼리에 대해서 옵티마이저가 gender 컬럼을 건너뛰고 birth_date 컬럼만으로도 인덱스 검색이 가능하게 해주는 인덱스 스킵 스캔이라는 최적화 기능이 도입되었습니다.
SELECT gender, birth_date FROM employees WHERE gender ='M' AND birth_date>='1965-02-01';
SELECT gender, birth_date FROM employees WHERE gender ='F' AND birth_date>='1965-02-01';ㅡ
그래서 인덱스 스킵 스캔이 실행되면 위와 같은 쿼리를 실행하는것과 비슷하게 최적화를 실행하게 됩니다.
💡 인덱스 스킵 스캔은 WHERE 조건절에 조건이 없는 인덱스의 선행 컬럼의 유니크한 값의 갯수가 적어야하고 커버링 인덱스이어야 한다. 커버링 인덱스이어야 하는건 다른 컬럼도 필요하기 때문에 풀 테이블 스캔으로 실행되기 때문이다.비교 조건에 따라 인덱스 동작방식이 어떻게 달라지는지 알아보기 위해 dept_emp 테이블에 dept_no와 emp_no 컬럼이 존재한다고 가정하겠습니다.
전문 검색은 말 그대로 Full Text Search를 의미하는데 B-Tree와는 다르게 전체 텍스트 검색을 하는 인덱스 입니다. 이러한 인덱스는 2가지의 알고리즘으로 구분할 수 있는데 아래와 같습니다.
멀티 밸류 인덱스는 제 1정규화에 위배되는 형태로 되어있는 Json 타입으로 정의된 레코드처럼 인덱스 키와 데이터 레코드가 1대1이 아닌 하나의 데이터 레코드가 여러 개의 키 값을 가질 수 있는 형태의 인덱스 입니다.
아래와 같은 함수들을 통해 해당 인덱스를 활용할 수 있습니다.
클러스트링이란 여러 개를 하나로 묶는다는 의미로 사용되는데 클러스터링 인덱스는 프라이머리 키를 기준으로 비슷한 것들끼리 묶어서 저장하는 형태로 구현됩니다. 이러한 특징으로 인해 프라이머리 키 값에 의해 레코드의 저장 위치가 결정됩니다.
💡 B-Tree 인덱스 또한 인덱스 키 값으로 정렬되어 저장되는데 어떻게 보면 이러한 인덱스랑 클러스터링된 것으로 이해할 수 있는데 클러스터링 인덱스는 프라이머리키를 통해 정렬되기 떄문에 B-Tree 인덱스는 클러스터링 인덱스라고 부르지 않습니다.B-Tree와 달리 클러스터링 리프노드에는 레코드의 모든 컬럼이 같이 저장돼 있습니다. 그리고 InnoDB 스토리지 엔진이 적절한 클러스터링 키 후보를 찾지 못하는 경우 내부적으로 레코드의 일련번호 컬럼을 생성합니다.
장점
단점