인덱스는 어떻게 작동하나? with MySQL

Myungseo Kang bio photo By Myungseo Kang

데이터베이스의 읽기(Read) 성능 향상을 위해 가장 먼저 생각하곤 하는 것이 바로 인덱스(Index)입니다.

평소에 관심이 많았던 인덱스의 작동 방식과 장단점 을 알아보려고 합니다.

인덱스(Index)란 무엇인가?

책에 있는 색인(마찬가지로 Index라 불리운다.)의 경우를 예로 들 수 있을 것 같습니다.

1000페이지짜리 책에서 특정 내용을 소개하는 페이지를 알고 싶다고 가정해보겠습니다.

색인 페이지가 없다면 일일이 1페이지부터 혹은 랜덤하게 이곳저곳 찾아보겠죠. 운이 나쁘다면 굉장히 오래 걸릴 것입니다.

하지만 색인 페이지가 있다면 해당 내용이 몇번째 페이지에서 소개되는지를 빠르게 파악할 수 있습니다.

이와 비슷하게 DBMS에서도 모든 데이터를 검색해서 원하는 결과를 가져오려면 해당 테이블의 내용이 많으면 많을수록 오래 걸립니다.

그래서 컬럼의 값과 해당 레코드의 주소를 key-value(키와 값) 형태로 만들어두는 것입니다.

하지만 이러한 인덱스나 색인도 많아지다보면 찾는 데 시간이 오래 걸리게 됩니다.

따라서 색인 같은 경우는 가나다순으로 적어두곤 합니다.

마찬가지로 인덱스를 저장할 때도 정렬이 중요합니다.

인덱스(Index)의 장단점

이제 인덱스와 일반 데이터를 자료구조에 비교해보겠습니다.

항상 정렬되어 있는 리스트와 일반 리스트가 있다고 가정해보겠습니다.

이 리스트들에 새로운 값을 추가하면 어떤 일이 벌어질까요?

항상 정렬된 리스트의 경우, 값이 정렬된 상태를 유지하여야 하기 때문에 맨 뒤에 넣지 못하고, 정렬 기준에 따라 정해진 위치에 삽입해야 합니다.

하지만 일반 리스트의 경우, 리스트의 맨 뒤에 값을 추가하기만 하면 됩니다.

따라서 쓰기 작업의 경우 일반 데이터를 추가하는 것이 훨씬 빠를 겁니다.

다만, 읽기 작업의 경우 이미 정렬되어 있는 값에서는 원하는 값을 찾기 아주 용이하죠.

이는 DBMS에서도 마찬가지로 적용됩니다.

인덱스가 많이 걸려있는 테이블의 경우 INSERT, UPDATE, DELETE 와 같은 쓰기 작업의 속도가 느려집니다.

이유는 위에서 말했듯이 정렬된 상태를 유지해야하기 때문이죠.

다만 읽기 작업인 SELECT의 경우에는 굉장히 빠르게 처리할 수 있습니다.

정리하자면 인덱스는 쓰기 작업의 속도를 희생하여, 읽기 작업의 성능을 높이는 기능 입니다.

따라서 무조건 인덱스를 많이 걸게 되면 오히려 성능에 더 안좋은 영향을 끼칠 수 있으므로 신중해야 합니다.

인덱스의 종류

  1. B-Tree 인덱스
  2. Hash 인덱스
  3. Fractal-Tree 인덱스

이렇게 3가지를 꼽을 수 있고, 한가지씩 설명해보겠습니다.

1. B-Tree 인덱스

B-Tree 인덱스는 가장 보편적으로 사용되고 있는 인덱스의 방식입니다. (여기서 B는 “Balanced” 를 의미한다고 합니다.)

B-Tree 인덱스는 원래 값을 변형시키지 않고, 인덱스 구조체 내에서 항상 정렬된 상태로 값을 저장하고 있습니다.

특수한 경우를 제외한 가장 일반적인 용도에 적합한 인덱스 방식입니다.

구조 및 특징

B-Tree 의 구조는 트리 최상위의 “루트 노드”가 존재하고, 최하단에 “리프 노드”들이 여러개 존재합니다.

그리고 루트 노드와 리프 노드 사이의 모든 노드들은 “브랜치 노드”라고 불립니다.

인덱스의 리프 노드들은 항상 실제로 데이터가 있는 주소값을 저장하고 있습니다.

따라서 B-Tree에서 특정한 값이 검색되는 흐름은 루트 노드에서 특정 검색 조건에 의해 탐색을 진행하고, 탐색을 통해 도달한 리프 노드가 가지고 있는 주소값에 해당하는 데이터를 받게 되는 것입니다.

그리고 새로운 컬럼이 추가될 때, 인덱스에 새로운 키가 추가되는 과정을 보겠습니다.

새로운 키값이 B-Tree에 저장될 때 저장될 키값을 이용해 B-Tree 상의 적절한 위치를 검색해서 저장해야 합니다.

저장될 위치가 정해지면 해당 데이터의 주소값을 B-Tree의 리프 노드에 저장하게 됩니다.

만약 리프 노드가 꽉찬 상태라면 상위 브랜치 노드까지 처리 범위가 넓어지기 때문에, B-Tree는 쓰기 작업이 비용이 많이 드는 것으로 알려져 있습니다.

B-Tree에서 키값이 삭제되는 경우는 그냥 간단하게 리프 노드를 찾아서 삭제만 하면 된다.

키값이 변경되는 경우는 간단히 주소값만 바꾸면 좋겠지만 그렇게는 구조상 가능하지 않습니다.

따라서 해당 키값을 삭제하고, 새롭게 추가하는 방식으로 작동됩니다.

이제 궁극의 키값이 검색될 때 작동 방식을 알아보겠습니다.

쓰기 작업(INSERT, UPDATE, DELETE)할 때 성능 저하를 무릅쓰고도 B-Tree 인덱스를 사용하는 이유는 빠르게 검색해야되기 때문입니다.

인덱스의 검색 작업은 SELECT 뿐만 아니라 UPDATE, DELETE 작업에서 원하는 값을 찾아야할 때도 사용됩니다.

B-Tree 인덱스는 100% 일치 또는 값은 앞부분만 일치하는 경우에 사용이 가능합니다.

부등호(>, <) 비교나 값의 뒷부분에 일치하는 검색을 하는 경우에는 B-Tree 인덱스도 소용이 없습니다.

그리고 B-Tree 인덱스 검색 작업에서 중요한 점은 원래 값에 대한 인덱스만 저장하고 있기 때문에 특정 함수나 연산을 통해 도출된 값에는 B-Tree 인덱스의 장점을 살릴 수가 없다는 점을 유의해야 합니다.