인덱스 최적화(Index Optimization)

송현진·2025년 7월 5일
0

DataBase

목록 보기
5/10

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

인덱스는 데이터베이스에서 원하는 데이터를 빠르게 찾을 수 있도록 도와주는 기능이다. 예를 들어 책에서 특정 단어를 찾기 위해 처음부터 페이지를 넘기기보다는 맨 뒤에 있는 색인을 활용해 해당 페이지를 빠르게 찾는 것처럼 인덱스는 데이터베이스 내에서 원하는 데이터를 더 적은 비용으로 검색할 수 있게 해준다.

데이터베이스에서 인덱스는 주로 B-Tree 형태로 저장된다. 이 구조는 이진 탐색처럼 원하는 값을 빠르게 좁혀나가는 방식으로 동작하여 전체 데이터를 훑지 않고도 빠른 조회가 가능하다. 반대로 인덱스가 없다면 조건에 맞는 데이터를 찾기 위해 테이블의 모든 레코드를 처음부터 끝까지 순차적으로 탐색해야 하며 이를 Full Table Scan이라고 한다. 데이터가 많을수록 성능은 급격히 저하된다.

인덱스는 언제 필요할까?

모든 컬럼에 인덱스를 걸면 성능이 좋아질 것처럼 보이지만 오히려 비효율을 초래할 수 있다. 인덱스는 읽기 속도를 높이는 대신 쓰기 성능(INSERT, UPDATE, DELETE)을 떨어뜨리는 부작용이 있기 때문이다. 따라서 인덱스는 다음과 같은 경우에 선별적으로 사용하는 것이 좋다.

  • WHERE 절에 자주 등장하는 컬럼은 빠른 조건 검색을 위해 인덱스가 필요하다.
  • JOIN 시 기준이 되는 외래키 컬럼은 효율적인 조인을 위해 인덱스를 활용해야 한다.
  • ORDER BY, GROUP BY에 사용되는 컬럼은 정렬/집계 과정에서 성능에 영향을 미치므로 인덱스를 고려할 수 있다.
  • 중복도가 낮고 선택도가 높은 컬럼은 인덱스를 통한 필터링 효과가 뛰어나다.

반면 성별이나 국가처럼 중복된 값이 많은 컬럼은 인덱스를 걸어도 효과가 별로 없고 데이터의 삽입/수정이 빈번한 컬럼은 인덱스 갱신 비용이 커져 전체 성능 저하를 유발할 수 있다.

인덱스 최적화란?

인덱스 최적화란 조회 성능을 높이기 위해 인덱스를 적절한 컬럼에 효율적인 방식으로 설정하고 불필요한 인덱스를 제거함으로써 데이터베이스의 읽기/쓰기 성능을 균형 있게 개선하는 작업이다.

인덱스 성능 최적화 방법

1. 단일 인덱스 vs 복합 인덱스

단일 인덱스는 하나의 컬럼에만 적용된다. email = 'abc@example.com'처럼 단일 조건을 자주 검색하는 경우에 적합하다. 그러나 여러 조건을 동시에 필터링하는 쿼리에서는 단일 인덱스만으로는 효율이 떨어질 수 있다.

복합 인덱스는 두 개 이상의 컬럼을 묶어서 하나의 인덱스로 구성한다. WHERE user_id = ? AND status = ?처럼 여러 조건이 함께 쓰이는 쿼리에 유리하다. 하지만 주의할 점은 인덱스 컬럼의 순서가 매우 중요하다는 것이다. (user_id, status) 순서로 구성된 복합 인덱스는 user_id만 조회할 때는 잘 동작하지만 status만 단독으로 조회할 경우엔 인덱스를 사용할 수 없을 수 있다. 이를 "인덱스의 시작 컬럼 규칙(Left-most Prefix Rule)"이라고 한다.

2. 커버링 인덱스

커버링 인덱스란 쿼리에서 사용되는 모든 컬럼이 인덱스에 포함되어 있어 인덱스만으로 결과를 반환할 수 있는 경우를 말한다. 이런 쿼리는 테이블 자체를 접근할 필요가 없어 훨씬 빠르게 실행된다.

예를 들어 다음 쿼리를 보자.

SELECT email FROM user WHERE email = 'abc@example.com';

email 컬럼에 인덱스가 설정되어 있고, 이 쿼리에서 참조하는 컬럼이 오직 email 하나뿐이라면 MySQL은 인덱스 자체만으로 결과를 가져오게 된다. 테이블에서 데이터를 다시 조회하지 않아도 되므로 I/O 비용이 매우 낮아지고 성능이 크게 향상된다.

3. 인덱스 선택도

선택도는 전체 데이터 중 특정 값이 얼마나 고유한지를 의미한다. 예를 들어 gender 컬럼처럼 "남"과 "여"로만 나뉘는 경우에는 많은 행이 같은 값을 가지므로 선택도가 낮다. 반면 email, user_id처럼 각 행마다 값이 다른 컬럼은 선택도가 높다.

선택도가 높을수록 인덱스 효율이 좋아지며 쿼리가 빠르게 필터링될 가능성이 높다. 따라서 인덱스를 설정할 때는 해당 컬럼의 선택도를 반드시 고려해야 한다. MySQL에서는 다음 쿼리로 선택도를 추정해볼 수 있다.

SELECT COUNT(DISTINCT column_name) / COUNT(*) FROM table_name;

0.1 이하라면 선택도가 낮고 0.5 이상이라면 인덱스 효율이 좋은 편이다.

4. 인덱스를 사용하지 못하는 경우

인덱스를 설정했더라도 다음과 같은 상황에서는 인덱스가 무시되고 Full Table Scan이 발생할 수 있다.

  • LIKE '%값%'처럼 와일드카드가 앞에 붙으면 인덱스가 사용되지 않는다. (뒤에만 붙은 경우 %값은 사용 가능)
  • OR 조건이 서로 다른 컬럼을 포함할 경우 인덱스가 적용되지 않을 수 있다.
  • WHERE DATE(created_at) = '2025-07-05'처럼 컬럼에 함수나 연산이 적용되면 인덱스를 사용할 수 없다. 이럴 땐 BETWEEN을 사용하거나 컬럼의 원본 값을 그대로 활용하는 방식이 필요하다.
  • 데이터가 너무 적을 경우 DB는 인덱스를 쓰는 것보다 전체 데이터를 스캔하는 게 더 빠르다고 판단해 인덱스를 생략한다.

인덱스 사용 여부 확인하기

MySQL에서는 EXPLAIN 키워드를 통해 쿼리 실행 계획을 확인할 수 있다. 이 기능을 사용하면 해당 쿼리에 어떤 인덱스가 사용되었는지 어떤 방식으로 테이블을 탐색하는지 등을 알 수 있다. 이는 인덱스가 실제로 적용되고 있는지 확인하고 불필요한 Full Table Scan이 발생하는지를 파악하는 데 중요한 도구다.

EXPLAIN SELECT * FROM user WHERE email = 'abc@example.com';

EXPLAIN 결과에서 typeALL이면 Full Table Scan을 의미하고 ref, range, const 등은 인덱스를 사용한 것이다. key 항목에는 실제로 사용된 인덱스명이 표시된다.

실행 계획에 표시되는 각 항목이 무엇을 의미하고 어떻게 분석해야 하는지는 다음 TIL에서 EXPLAIN 키워드를 중심으로 상세히 다룰 예정이다.

인덱스 설계 시 주의사항

  • 쓰기 성능 저하: 인덱스는 삽입/수정/삭제 시마다 자동으로 갱신된다. 인덱스가 많을수록 이 과정에 소요되는 시간이 증가하며 대량 데이터 쓰기 시 병목이 될 수 있다.
  • 디스크 공간 증가: 인덱스는 별도의 자료구조로 저장되므로 디스크를 추가로 사용한다. 특히 여러 복합 인덱스를 동시에 생성하면 공간 낭비가 발생할 수 있다.
  • 불필요한 인덱스는 제거해야 한다: 사용되지 않는 인덱스는 읽기에도 도움되지 않으며 오히려 쓰기 작업만 방해한다. SHOW INDEX FROM 테이블명으로 불필요한 인덱스를 식별하고 주기적으로 정리하는 것이 좋다.
  • 복합 인덱스의 순서를 신중하게 설계하자: 가장 자주 필터링되는 컬럼을 앞에 배치해야 인덱스가 제대로 작동한다. 순서가 맞지 않으면 기대했던 성능 향상을 얻지 못할 수 있다.

📝 배운점

이번에 인덱스를 공부하며 느낀 점은 인덱스는 단순히 "속도를 빠르게 해준다"는 차원을 넘어서서 데이터의 쓰기-읽기 균형을 고려한 정교한 설계가 필요한 도구라는 것이다. 특히 쿼리마다 인덱스가 정말 사용되고 있는지 EXPLAIN으로 직접 확인해보는 습관이 중요하며 선택도, 인덱스 순서, 커버링 여부 등을 종합적으로 고려해야 진짜 의미 있는 성능 개선이 가능하다는 것을 배웠다. 앞으로는 쿼리 튜닝이 필요할 때 무조건 인덱스를 추가하기보다는 실행 계획과 데이터의 특성을 바탕으로 정밀하게 인덱스를 설계하는 습관을 가져야겠다고 다짐하게 됐다.


참고

profile
개발자가 되고 싶은 취준생

0개의 댓글