PostgreSQL 기초 강좌 #4 인덱스는 언제 사용되는가

4 분 소요

쿼리가 느릴 때 가장 먼저 검토할 방법은 인덱스입니다. 인덱스가 무엇인지의 일반 원리는 별도 글에서 다뤘으므로, 이번 편은 PostgreSQL에서의 실무, 즉 만드는 법보다 만들었는데 왜 사용되지 않는가를 중심으로 다룹니다. 판별 도구인 EXPLAIN은 다음 편에서 본격적으로 다루고, 여기서는 원리와 규칙을 세우겠습니다.

B-tree: 기본 인덱스가 바꾸는 것 #

CREATE INDEX의 기본 타입은 B-tree입니다. 컬럼 값을 정렬된 트리로 유지해, 전체를 읽는 순차 탐색(Seq Scan) 대신 트리를 따라 내려가는 조회를 가능하게 만듭니다. 수백만 행에서 수 초 걸리던 조회가 밀리초로 내려가는 극적인 개선의 정체가 이것입니다.

인덱스 생성
CREATE INDEX idx_orders_user_id ON orders (user_id);

정렬을 유지하는 구조라서 = 만이 아니라 범위(<, >, BETWEEN), 정렬(ORDER BY), 그리고 LIKE 'abc%'(전방 일치)까지 받쳐 줍니다. 반대로 LIKE '%abc'(후방 일치)는 정렬 구조로 풀 수 없어 기본 B-tree 인덱스를 사용할 수 없습니다. PK와 UNIQUE 제약에는 인덱스가 자동으로 만들어지지만, 외래 키에는 자동으로 만들어지지 않는다는 것이 흔한 착각입니다. orders.user_id 같은 FK 컬럼은 조인과 부모 삭제 확인에 계속 쓰이므로, 인덱스를 직접 만드는 것이 기본기입니다.

사용되지 않는 이유 1: 복합 인덱스의 선행 컬럼 #

여러 컬럼의 복합 인덱스는 왼쪽부터 씁니다. 전화번호부가 (성, 이름) 순으로 정렬되어 있으면 성 없이 이름만으로는 찾을 수 없는 것과 같은 구조입니다.

복합 인덱스와 선행 컬럼
CREATE INDEX idx_orders_user_created ON orders (user_id, created_at);

-- 인덱스가 사용됩니다: user_id로 시작
WHERE user_id = 42 AND created_at >= '2026-12-01'
WHERE user_id = 42

-- 인덱스를 사용할 수 없습니다: 선행 컬럼(user_id) 없이 created_at만
WHERE created_at >= '2026-12-01'

복합 인덱스의 컬럼 순서는 “등호로 거는 컬럼을 앞에, 범위로 거는 컬럼을 뒤에"가 기본 규칙입니다. 심화 전략(부분·커버링·GIN 등)은 실전 편에서 다룹니다.

사용되지 않는 이유 2: 컬럼을 가공하면 사용할 수 없습니다 #

인덱스는 저장된 값 그대로 정렬되어 있습니다. 컬럼에 함수를 씌우면 그 정렬을 쓸 수 없습니다.

컬럼 가공의 함정
-- 인덱스를 사용할 수 없습니다: 컬럼에 함수 적용
WHERE lower(email) = 'kim@example.com';
WHERE created_at::date = '2026-12-25';

-- 인덱스가 사용됩니다: 컬럼은 그대로 두고 조건 쪽을 바꿉니다
WHERE email = 'kim@example.com';
WHERE created_at >= '2026-12-25' AND created_at < '2026-12-26';

날짜 비교를 범위로 바꾸는 두 번째 예는 실무에서 특히 자주 등장합니다. 어쩔 수 없이 가공된 값으로 찾아야 한다면 그 식 자체에 인덱스를 만드는 식 인덱스(CREATE INDEX ... ON users (lower(email)))가 해법입니다. 같은 계열의 함정으로 자료형 불일치(text 컬럼을 숫자와 비교하는 등)도 암묵 형변환이 컬럼 쪽에 적용되며 인덱스를 무력화합니다.

사용되지 않는 이유 3: 사용하지 않는 것이 더 빠른 경우 #

인덱스가 있어도 플래너가 일부러 사용하지 않는 경우가 있습니다. 대표적으로 선택도가 낮은 조건입니다. status = 'done'이 전체의 90%라면, 인덱스로 90%의 행을 하나씩 찾아가는 것보다 그냥 전체를 순서대로 읽는 쪽이 쌉니다. 테이블이 아주 작을 때도 마찬가지입니다. 즉 “Seq Scan이 나온다 = 문제"가 아니라, 그 조건이 행을 충분히 좁히는지가 판단 기준이고, 이 판단을 눈으로 확인하는 도구가 다음 편의 EXPLAIN입니다.

대가: 인덱스는 공짜가 아닙니다 #

인덱스 하나는 “그 테이블에 대한 또 하나의 정렬된 사본"입니다. INSERT·UPDATE·DELETE마다 모든 인덱스가 같이 갱신되므로, 인덱스가 늘수록 쓰기가 느려지고 저장 공간이 늘어납니다. “혹시 몰라서” 만들어 둔 인덱스는 순수한 쓰기 세금입니다. 그래서 실무 순서는 명확합니다. 미리 상상해서 만드는 것이 아니라, 실제 느린 쿼리의 WHERE·JOIN·ORDER BY 컬럼을 보고 만듭니다. 무엇이 느린지 찾는 체계(pg_stat_statements)와 사용되지 않는 인덱스의 정리는 실전 편의 주제입니다.

정리 #

  • 기본 B-tree 인덱스는 등호, 범위, 정렬, 전방 일치 LIKE를 받칩니다. FK 컬럼에는 자동 생성이 없으므로 직접 겁니다.
  • 복합 인덱스는 왼쪽부터입니다. 등호 컬럼을 앞에, 범위 컬럼을 뒤에 두는 것이 순서의 기본 규칙입니다.
  • 컬럼을 가공하면 인덱스를 사용할 수 없습니다. 조건 쪽을 바꾸거나(날짜는 범위 비교로), 식 인덱스를 씁니다. 자료형 불일치도 같은 계열입니다.
  • 선택도가 낮으면 인덱스를 사용하지 않는 것이 정상입니다. Seq Scan은 죄가 아니라 판단의 결과일 수 있습니다.
  • 인덱스는 쓰기 세금입니다. 상상이 아니라 실측된 느린 쿼리를 보고 만드는 것이 순서이고, 그 관찰 도구(EXPLAIN)를 다음 편에서 다루겠습니다.
X