데이터베이스 인덱스

이재민·2023년 11월 20일

인덱스

인덱스란 데이터베이스에서 검색 성능을 향상시키기 위해 사용되는 자료구조입니다.

인덱스 구분

인덱스는 데이터를 관리하는 방식(알고리즘)과 중복 값의 허용 여부 등에 따라 여러 가지로 나눠볼 수 있습니다.

인덱스를 역할별로 구분해 본다면 프라이머리 키(Primary Key), 세컨더리 키(Secondary Key)로 구분할 수 있습니다.

  • 프라이머리 키는 레코드를 대표하는 컬럼의 값으로 만들어진 인덱스를 의미합니다.
  • 프라이머리 키를 제외한 나머지 모든 인덱스는 세컨더리 인덱스로 분류할 수 있습니다.
    유니크 인덱스는 프라이머리 키와 성격이 비슷해 대체할 수 있지만 그냥 세컨더리 인덱스로 분류하기도 합니다.

데이터 저장 방식(알고리즘)으로 구분

상당히 많은 분류가 가능하지만 대표적으로 B-Tree 인덱스, Hash 인덱스로 구분할 수 있습니다. 대표적으로 두 인덱스는 시중의 RDBMS에서 많이 사용하는 알고리즘입니다.

B-Tree 알고리즘

이후 설명할 것이지만, 가장 일반적으로 사용되는 인덱스 알고리즘으로서 상당히 오래전에 도입된 알고리즘이기에 성숙해진 상태입니다.
B-Tree 인덱스는 칼럼의 값을 변형하지 않고 원래의 값을 이용해 인덱싱하는 알고리즘입니다.

Hash 알고리즘

컬럼의 값으로 해시값을 계산해서 인덱싱하는 알고리즘으로 매우 빠른 검색을 지원합니다.
하지만, 값을 변형해서 인덱싱하므로 전방(Prefix) 일치와 같이 값의 일부만 검색하거나 범위를 검색할 때는 해시 인덱스를 사용할 수 없습니다.
주로 메모리 기반의 데이터베이스에서 많이 사용합니다.

데이터 중복 허용으로 구분

유니크 인덱스(Unique), 유니크 하지 않는 인덱스(Non-Unique)로 구분할 수 있습니다.
실제 DBMS의 쿼리를 실행해야 하는 옵티마이저에게는 상당히 중요한 문제가 될 수 있습니다.
유니크 인덱스에 동등 조건(Equal, =)으로 검색한다는 것은 항상 1건의 레코드만 찾으면 더 이상 찾지 않아도 된다는 것을 옵티마이저에게 알려주는 효과를 냅니다.

B-Tree 인덱스

B-Tree 인덱스 방식은 칼럼의 원래 값을 변형하지 않고 인덱스 구조체 내에서는 항상 정렬된 상태로 유지합니다.
가장 일반적으로 사용되는 인덱스 알고리즘으로서 상당히 오래전에 도입된 알고리즘이기에 성숙해진 상태입니다.

구조

B-Tree는 트리 구조의 최상위에 루트 노드(Root node) 그 하위에 자식 노드가 존재하는 형태입니다.
가장 하단의 노드를 리프 노드(Leaf node), 루트 노드, 리프 노드를 제외한 중간 노드를 브랜치 노드(Branch node)라고 합니다.
데이터베이스에서 인덱스와 실제 데이터가 저장된 데이터는 따로 관리되는데, 리프 노드는 항상 실제 데이터 레코드를 찾아가기 위한 주솟값을 가지고 있습니다.

특성

인덱스의 키 값은 모두 정렬돼 있지만, 데이터 파일의 레코드는 정렬돼 있지 않고 임의의 순서로 저장되어 있습니다.
많은 사람들이 데이터 파일의 레코드는 INSERT 된 순서대로 저장된다고 생각하지만 그렇지 않습니다. 물론 INSERT만 수행된다면 맞을 수도 있습니다.
하지만, 레코드가 삭제되어 빈 공간이 생기면 그 다음의 INSERT는 삭제된 공간을 재활용하기 때문입니다.

선택도(기수성)

인덱스에서 선택도(Selectivity) 또는 기수성(Cardinality)는 거의 같은 의미로 사용되며, 모든 인덱스 키 값 가운데 유니크한 값의 수를 의미합니다.
전체 인덱스 키 값은 100개인데, 그중에서 유니크한 값이 10개라면 기수성(Cardinality)는 10입니다.

중복된 값이 많아지면, 기수성은 낮아지고, 선택도 또한 떨어집니다.
인덱스는 선택도가 높을수록 검색 대상이 줄어들기 떄문에 그만큼 빠르게 처리됩니다.
그래서 기수성이 낮은 컬럼으로 인덱스를 설정하면 인덱스 역할을 제대로 수행하지 못하게 됩니다.

B-Tree 인덱스 정렬 및 스캔

인덱스를 생성할 때 정렬 규칙(오름차순, 내림차순)에 따라 인덱스의 키 값이 정렬되어 저장됩니다.
인덱스 정렬 규칙이 정해졌다고 그 규칙으로만 사용되는 것은 아니고 쿼리에 따라 옵티마이저가 실시간으로 만들어내는 실행 계획에 따라 결정됩니다.

인덱스의 정렬

MySQL 5.7 버전까지 컬럼 단위로 정렬 순서를 혼합해서 인덱스를 생성할 수 없었다. 하지만 DBMS 툴에서 사용할 수 있었던 것은 단순히 버전 호환성을 위해 문법만 제공된 것이다. MySQL 8.0 버전부터는 정렬 순서를 혼합해서 인덱스 생성이 가능하다

인덱스 스캔

인덱스는 항상 오름차순으로만 정렬되어 있지만, 쿼리에 따라 인덱스를 역순으로 접근해야되는걸 옵티마이저는 알고 있습니다.
즉, 인덱스 생성 시점에 ASC or DESC 정렬이 결정되지만, 쿼리가 그 인덱스를 사용하는 시점에 인덱스를 읽는 방향에 따라 ASC, DESC 정렬 효과를 얻을 수 있습니다.

인덱스 스캔 방식

인덱스 레인지 스캔

대표적인 접근 방식으로 인덱스를 통해 레코드를 한 건만 읽는 경우 한 건 이상 읽는 경우를 쉽게 묶어서 얘기합니다.
해당 방식은 인덱스의 범위가 결정됐을 때 사용하는 방식입니다.
루트 노드 ~ 브랜치 ~ 리프 노드까지 찾아 들어가 레코드의 시작 지점을 찾을 수 있습니다.
시작위치부터 리프 노드의 레코드를 순서대로 읽으면 되는 방식입니다. 인덱스 키와 레코드 주소를 이용해 레코드가 저장된 페이지를 가져오고 최종 레코드를 읽어오는 방식입니다.
커버링인덱스는 디스크에서 최종 레코드를 읽어오는 과정이 필요없어 매우 빠릅니다.

인덱스 풀 스캔

인덱스 레인지 스캔 방식과 마찬가지로 인덱스를 사용하지만, 인덱스 레인지 스캔 방식과 달리 인덱스의 처음부터 끝까지 모두 읽는 방식입니다.
해당 방식은 모통 인덱스 컬럼 순서로 쿼리를 구성하지 않아 발생합니다.
그래도 테이블 풀 스캔보다는 효율적입니다. 인덱스의 전체 크기는 테이블 자체의 크기보다는 훨씬 작기 때문입니다.

루스 인덱스 스캔

루스 인덱스는 인덱스 레인지 스캔과 비슷하게 동작하지만, 중간에 필요치 않는 인덱스 키값은 무시(SKIP)하고 다음으로 넘어가는 형태로 처리합니다.
일반적으로 GROUP BY 또는 집계 함수 중 MAX(), MIN() 함수 최적화의 경우에 사용됩니다.
MySQL 5.7 버전까지는 제한적이었지만, MySQL 8.0부터 최적화를 지원하기 시작햇습니다.

인덱스 스킵 스캔

MYSQL 8.0 버전에 도입된 인덱스 스킵 스캔은 WHERE 조건절의 검색을 위해 사용 가능하도록 용도가 훨씬 넓어졌습니다.
인덱스 구성에 맞지 않는 쿼리를 수행했을해 인덱스를 효율적으로 사용하지 못할 경우에 옵티마이저가 최적화를 해줍니다.

다중 컬럼(Multi-column) 인덱스

다중 컬럼 인덱스는 두 개 이상의 컬럼으로 구성된 인덱스를 말합니다.

다중 컬럼 인덱스의 경우는 뒤 쪽 컬럼은 앞 쪽 컬럼에 의존해서 정렬됩니다. 때문에 인덱스 내에서 각 컬럼의 위치는 상당히 중요합니다.

profile
문제 해결과 개선 과제를 수행하며 성장을 추구하는 것을 좋아합니다.

0개의 댓글