데이터베이스 용어 사전
인덱스왼쪽 접두사 · leftmost prefix · 다중 칼럼 인덱스

복합 인덱스

여러 칼럼을 묶은 인덱스. 앞 칼럼부터 차례로 정렬되므로 왼쪽 접두사부터만 쓸 수 있다.

여러 칼럼을 하나로 묶은 인덱스. 칼럼 순서가 전부다.

왜 순서가 중요한가 — 전화번호부와 같다

INDEX (a, b, c)a로 먼저 정렬하고, a가 같은 것끼리 b로, b도 같으면 c로 정렬한다. 전화번호부가 성 → 이름 순인 것과 정확히 같다.

INDEX (a, b, c) 가 도움 되는 조회

  WHERE a = ?                       ✅
  WHERE a = ? AND b = ?             ✅
  WHERE a = ? AND b = ? AND c = ?   ✅
  WHERE b = ?                       ❌  a를 건너뛰면 정렬이 무의미
  WHERE a = ? AND c = ?             △  a까지만 인덱스, c는 읽어서 걸러냄

성을 모르면 전화번호부에서 이름만으로 못 찾는 것과 같다. 이 성질을 왼쪽 접두사(leftmost prefix) 규칙이라 한다.

칼럼 순서를 정하는 규칙

  • 등치(=) 조건 칼럼을 앞에, — 범위(> · BETWEEN) 칼럼을 뒤에

범위 조건이 걸린 칼럼 뒤부터는 인덱스 정렬을 쓸 수 없기 때문이다.

-- ❌ 나쁜 순서: created_at 이 범위라 status 정렬이 죽는다
CREATE INDEX idx_bad ON orders (created_at, status);

-- ✅ 좋은 순서: 등치 먼저, 범위 나중
CREATE INDEX idx_good ON orders (status, created_at);
SELECT * FROM orders
 WHERE status = 'PAID' AND created_at >= '2026-01-01';

ORDER BY까지 덤으로 해결된다

인덱스 순서가 정렬 순서와 맞으면 별도 정렬 연산이 사라진다.

-- idx_good 이 (status, created_at) 이므로 정렬이 이미 돼 있다
SELECT * FROM orders WHERE status = 'PAID' ORDER BY created_at LIMIT 20;

면접 함정

  • "칼럼마다 인덱스를 하나씩 만들면 복합 인덱스와 같다" → 다르다. 단일 인덱스 여러 개는 대개 하나만 선택되거나 비싼 병합을 거친다. 함께 쓰이는 조건은 하나의 복합 인덱스가 낫다.
  • "(a,b,c)가 있으면 (a,b)도 따로 필요하다" → 필요 없다. 왼쪽 접두사라 이미 커버된다.

정말 잘 쓰이는지 확인하기

-- PostgreSQL: Index Cond 와 Filter 를 구분해서 본다
EXPLAIN ANALYZE
SELECT * FROM orders WHERE status = 'PAID' AND created_at >= '2026-01-01';
-- Index Cond: ((status = 'PAID') AND (created_at >= '2026-01-01'))   ← 둘 다 인덱스로 좁힘 (좋음)
-- Filter: (created_at >= '2026-01-01')                               ← 읽어서 걸러냄 (인덱스 미활용)

Index Cond에 들어가면 인덱스로 범위를 좁힌 것이고, Filter로 빠지면 읽은 뒤 버린 것이다. 칼럼 순서가 잘못됐는지 판단하는 가장 정확한 신호다.

-- MySQL: key_len 으로 인덱스를 몇 바이트까지 썼는지 본다
EXPLAIN SELECT * FROM orders WHERE status = 'PAID' AND created_at >= '2026-01-01';
-- key_len 이 status 칼럼 길이만큼이면 created_at 은 못 쓴 것이다

안 쓰는 인덱스 찾아내기

-- PostgreSQL: idx_scan 이 0이면 한 번도 안 쓰인 인덱스다
SELECT relname, indexrelname, idx_scan, pg_size_pretty(pg_relation_size(indexrelid))
  FROM pg_stat_user_indexes WHERE idx_scan = 0 ORDER BY pg_relation_size(indexrelid) DESC;

안 쓰는 인덱스는 순수 부채다 — 쓰기마다 갱신되고 디스크를 먹는다. 지우기 전 영향을 보고 싶으면 MySQL 8.0+의 ALTER TABLE ... ALTER INDEX ... INVISIBLE로 먼저 숨겨 본다.

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — 인덱스 — B+Tree·클러스터형·커버링(PostgreSQL·MySQL)