김지팡의 저장소
Published 2025. 1. 10. 13:38
인덱스 이해하기 TIL
728x90

이번 포스팅에서는 인덱스에 대해서 이야기해보고자 한다.

 

인덱스는 개발하면서 여기저기에 자주 나오는 용어이지만, 막상 인덱스가 뭐예요?라는 질문을 받으면 수박 겉핥기식의 답변만 내놓았었다.

 

때문에 이번 기회에 인덱스를 제대로 학습하고 정리해보려 한다.

 

 

📌 인덱스

 

인덱스는 데이터를 빠르게 검색하도록 도와주는 도구인데, 데이터의 위치를 참조하거나 정렬된 구조를 가진다.

 

인덱스는 마치 도서관의 책을 찾는데, 위치를 모르고 찾는 것과 위치를 알고 찾는 것의 차이라고 볼 수 있다. 도서관이 데이터베이스이고, 책이 테이블의 데이터, 책의 위치가 인덱스인 것과 같다. 때문에, 인덱스로 인해 데이터베이스의 테이블 내 데이터를 효율적이고 빠르게 찾을 수 있게 된다.

 

인덱스가 데이터를 빠르게 찾도록 도와주는 도구라면, 인덱스를 무조건 설정하는 것이 좋지 않을까에 대해서 생각해볼 수 있다.

 

 

📌 인덱스를 설정하는 것이 무조건 좋을까?

 

결론부터 말하면 아니다. 사실 필자도 아직 인덱스를 가장 효율적으로 설정하는 방법에 대해서 정확히 숙지하고 있는 것은 아니지만, 인덱스를 설정하는 것이 무조건 좋은 것이 아니라는 것은 확실하다.

 

그 이유는 인덱스가 활용되는 내부 동작 방식을 이해하면 알 수 있다.

 

 

📌 인덱스 내부 동작 방식

 

내부 동작 방식을 [인덱스 설정] - [데이터 생성] - [인덱스 조회] - [데이터 조회] 3가지 파트로 나누어 이야기해보겠다.

 

🔗 [인덱스 설정]
영화 정보를 담고 있는 movie 테이블에 ‘title’ 필드에 단일 인덱스를 설정했다고 가정하겠다. 이때, MySQL 기준 title용 B-Tree 인덱스가 생성된다.

 

🧑‍💻 인덱스의 종류로는 여러 가지가 있는 것으로 알고, DB 마다 사용하는 인덱스가 다른 것으로 알고 있다!

 

🔗 [데이터 생성]
이후, movie에 row(데이터)가 생성될 때마다 생성된 title의 값이 title 용 B-Tree 인덱스에 저장이 된다.

 

예를 들어, title을 ‘Spiderman’으로 가지는 movie row와 title을 ’Ironman’으로 가지는 movie row가 삽입된다면, title 용 B-Tree 인덱스에 title 값인 ‘Spiderman’과 ‘Ironman’과 더불어, 테이블 내에 이들이 속한 페이지 정보를 함께 저장한다.

 

여기에서 페이지 정보는 ‘Spiderman’과 ‘Ironman’을 title로 가지는 row의 대략적인 위치를 말하는 것이다.

 

해당 페이지에는 title을 ‘Spiderman’이나 ‘Ironman’으로 가지는 다른 row들도 함께 있을 수도 있고, 다른 title 값을 가지는 row들이 있을 수도 있다.

 

필자는 왜 정확한 위치를 저장하지 않고 ‘대략적인 위치’를 저장하는지 궁금했다.

 

찾아보니 이유는 이러했다. 데이터의 삽입, 수정, 삭제가 일어날 때, 위치 정보가 바뀔 수 있기 때문이었다. 이런 상황에서 정확한 위치 정보를 저장하게 되면 인덱스를 매번 업데이트해줘야 하는 비용이 추가적으로 들기 때문이었다.

 

🔗 [인덱스 조회]

SELECT * FROM movie WHERE title = ‘Spiderman'; 쿼리를 실행한다고 가정하겠다. 쿼리를 실행하게 되면, 내부적으로 쿼리 옵티마이저(Query Optimizer)가 title 필드에 대해 인덱스 설정이 되어 있는지를 확인한다.

 

인덱스 설정이 되어 있으면, title 용 B-Tree 인덱스에서 ‘Spiderman’을 탐색 알고리즘(시간 복잡도 O(log n)을 가짐)을 활용해 찾는다. 이후, 인덱스로부터 title을 ‘Spiderman’으로 가지는 row(1개 혹은 그 이상)의 위치가 담긴 페이지 정보를 반환받는다.

 

🔗 [데이터 조회]

인덱스로부터 반환받은 페이지 정보에는 찾고자 하는 정보가 포함은 되어 있지만, 말 그대로 포함이 되어 있을 뿐이기에 해당 페이지 내에서 정확한 정보를 찾아야 하는 작업이 수행된다.

 

정확한 정보를 찾으면 최종적으로 사용자에게 row가 반환된다.

 

 

📌 인덱스를 막 설정한다면?

 

위에서 인덱스가 어떻게 설정되고, 이후에 어떻게 활용되는지를 알아보았다. 그렇다면, 인덱스를 설정하는 것이 성능상 무조건 이점이 아닌 이유에 대해 이야기해 보겠다.

 

movie 테이블 내에 title을 ‘spiderman’으로 가지는 row가 90% 이상이라고 가정하겠다.

 

이때, SELECT * FROM movie WHERE title = ‘spiderman’; 쿼리를 실행하면, title을 ‘spiderman’으로 가지는 row의 위치가 담긴 페이지 정보를 반환받고, 실제 테이블 내에서 그 정보들을 읽어내야 한다.

 

테이블의 전체 데이터 중 90%에 육박하는 row의 인덱스를 조회하고, 그 데이터를 다시 조회해야 되는 작업은 인덱스를 사용하지 않고 전체 테이블을 순차적으로 스캔하는 Full table scan보다 비효율적일 수 있다.

 

그러기 때문에 이 상황에서는 title에 인덱스를 설정하지 않는 것이 더 좋다.

 

인덱스를 설정하는 것이 무조건 좋지는 않다는 것을 알았지만, Full table scan이 더 좋은지 인덱스를 활용한 방법이 더 좋은지는 어떻게 알 수 있을까?

 

이것은 쿼리의 실행 계획을 통해 알 수 있는데, 실행하고자 하는 쿼리 앞에 EXPLAIN 키워드를 붙이면 어느 인덱스가 활용됐는지 볼 수 있다.

 

쿼리 실행 계획을 세우는 역할은 위에서 잠시 언급됐던 쿼리 옵티마이저가 수행한다.

 

 

📌 쿼리 옵티마이저가 하는 일

 

쿼리를 실행할 때에는 무조건 쿼리 옵티마이저가 실행될 쿼리에 대해서 어떠한 방식으로 실행하는 것이 가장 효율적이고 저비용인지를 계산하여 계획을 세운다.

 

이것을 쿼리 실행 계획이라고 한다.

 

그래서 아무리 인덱스가 설정이 되어 있다고 해도, 쿼리 옵티마이저에 의해 인덱스를 활용하는 것보다 Full table scan을 하는 것이 더 효율적이라고 판단이 되면 인덱스가 활용되지 않는다.

 

인덱스를 설정하면, 데이터의 삽입, 삭제, 변경이 일어날 때마다 인덱스도 변경되기 때문에 추가적인 비용이 든다.

 

그래서 쿼리 실행 계획을 세워 인덱스를 활용할 수 없는 필드라면 인덱스를 설정하지 않는 것이 성능상 더 좋다.

 

 

🧑‍💻 회고

 

이번 포스팅에서는 인덱스에 대해서 알아보았다. 학습하면서 생각보다 인덱스에 대해 몰랐던 것이 너무 많았고, 그다지 쉬운 개념인 것도 아니라는 생각이 들었다.

 

전문가 수준의 학습은 아니겠지만, 이번 기회로 어떻게 인덱스를 활용해야 할지 배울 수 있게 되었다.

 

기회가 된다면 꼭 쿼리 튜닝에 대한 포스팅을 해보겠다 !

728x90
profile

김지팡의 저장소

@김지팡

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!