1. 개요

“느린 쿼리는 인덱스를 걸면 된다” 는 말은 반은 맞고 반은 틀리다. 인덱스가 왜 빠른지 원리를 모르면, 아무 컬럼에나 인덱스를 걸어놓고 오히려 느려지는 경우를 만든다. 지난 글 에 이어 이번엔 인덱스가 실제로 어떻게 동작하는지, 그리고 언제 도움이 안 되는지를 정리한다.


2. 핵심 내용

2-1. 인덱스 없이 찾으면 무슨 일이 생기나

인덱스가 없는 테이블에서 특정 값을 찾으려면 DB 는 첫 행부터 마지막 행까지 전부 훑어야 한다. 이걸 풀 테이블 스캔(Full Table Scan)이라고 부른다. 행이 100만 개면 최악의 경우 100만 번을 비교한다. 데이터가 늘어날수록 이 비용은 그대로 늘어난다(O(n)).

2-2. B-Tree 를 그림 없이 이해하기

대부분의 RDBMS 인덱스는 B-Tree(정확히는 B+Tree) 구조를 쓴다. 사전을 찾는 과정과 비슷하다. 사전에서 “고양이” 를 찾을 때 첫 페이지부터 넘기지 않는다. 먼저 자음 순서로 챕터를 찾고, 그 안에서 다시 페이지를 좁혀 들어간다.

B-Tree 도 같은 방식이다.

  • 루트 노드: “이 값보다 작으면 왼쪽 가지, 크면 오른쪽 가지” 처럼 큰 범위로 방향을 정한다.
  • 중간 노드(브랜치): 범위를 계속 좁혀 나간다.
  • 리프 노드: 실제 값과, 그 값이 있는 실제 행의 위치(또는 PK)를 들고 있다.

정렬된 값들을 트리로 쌓아두고, 비교할 때마다 절반 가까이를 버리면서 내려간다. 100만 건이어도 트리 높이는 보통 서너 단계 정도라 탐색 비용이 O(log n) 수준으로 줄어든다. 100만 번 비교하던 걸 20번 안팎으로 끝낸다는 뜻이다.

인덱스로 찾은 리프 노드가 들고 있는 게 PK 뿐이라면(보조 인덱스), 실제 컬럼 값을 더 읽어야 할 때 그 PK 로 테이블 원본을 한 번 더 찾아가야 한다. 이 과정을 흔히 “테이블 접근” 또는 “북마크 조회” 라고 부른다.

2-3. 인덱스가 오히려 느려지는 경우

인덱스를 걸었다고 항상 빨라지는 게 아니다.

카디널리티가 낮은 컬럼. is_deleted, gender 처럼 값의 종류가 몇 개 안 되는 컬럼에 인덱스를 걸면, 조건에 맞는 행이 전체의 절반 가까이 나온다. 인덱스로 후보를 추려도 결국 테이블 대부분을 다시 읽어야 해서, 옵티마이저는 차라리 처음부터 풀스캔하는 쪽을 고른다. 인덱스가 있어도 안 쓰이거나, 억지로 쓰면 더 느리다.

조회할 행 비율이 큰 경우. 조건에 맞는 행이 전체 테이블의 상당 부분(대략 몇 % 이상)이면, 인덱스로 하나씩 찾아 테이블을 왔다갔다 하는 것보다 처음부터 순서대로 쓸어읽는 게 디스크 I/O 관점에서 더 싸다. 인덱스 탐색은 임의 접근(random I/O)이고, 풀스캔은 순차 접근(sequential I/O)이라 단건 비용 자체가 다르다.

쓰기 비용. 인덱스가 많을수록 INSERT/UPDATE/DELETE 마다 그 인덱스들도 같이 갱신해야 한다. 조회는 거의 없고 쓰기가 잦은 테이블에 인덱스를 욕심껏 걸면 쓰기 성능이 떨어진다.

선행 와일드카드 검색. LIKE '%검색어' 처럼 앞이 아니라 뒤에서부터 매칭하는 조건은 정렬 순서를 활용할 수 없어 일반 B-Tree 인덱스를 타지 못한다.

결국 인덱스는 “조건에 맞는 행이 전체에서 충분히 적을 때” 효과적이다. 인덱스를 걸기 전에 실행계획(EXPLAIN)으로 옵티마이저가 그 인덱스를 실제로 쓰는지, 예상 행 수가 얼마인지 먼저 확인하는 습관이 필요하다.


3. 마무리

요약

  • 인덱스가 없으면 DB 는 풀 테이블 스캔으로 O(n) 비용을 치른다.
  • B-Tree 는 정렬된 값을 트리로 쌓아 탐색 비용을 O(log n) 으로 줄인다.
  • 보조 인덱스는 리프에서 PK 를 찾은 뒤 테이블을 한 번 더 찾아가는 비용이 붙는다.
  • 카디널리티가 낮은 컬럼, 조회 비율이 큰 조건, 선행 와일드카드 검색은 인덱스 효과가 없거나 역효과다.
  • 인덱스를 걸기 전에 EXPLAIN 으로 실제로 타는지 확인한다.

다음은 트랜잭션 격리 수준 이다.