데이터베이스 용어 사전
인덱스index-only scan · INCLUDE

커버링 인덱스

쿼리에 필요한 칼럼이 인덱스 안에 전부 있어 테이블을 아예 읽지 않는 경우. I/O가 크게 준다.

쿼리가 필요로 하는 모든 칼럼이 인덱스 안에 다 있어서, DB가 테이블에 가지 않고 인덱스만으로 답을 만드는 것. PostgreSQL 실행계획에서는 index-only scan으로 표시된다.

무엇을 생략하는가

[일반 인덱스 스캔]

  • ① 인덱스에서 조건에 맞는 항목을 찾는다
  • ② 그 항목이 가리키는 테이블 위치로 가서 행을 읽는다 ← 이 단계가 비싸다

[커버링]

  • ① 인덱스에서 찾는다
  • (끝)

②가 비싼 이유는 인덱스는 정렬돼 있지만 테이블은 흩어져 있어서다. 인덱스를 순서대로 훑어도 테이블 접근은 디스크 여기저기를 랜덤하게 오간다. 조건에 맞는 행이 많을수록 이 비용이 폭증한다.

만드는 법

-- PostgreSQL: INCLUDE 로 '검색에는 안 쓰지만 값은 필요한' 칼럼을 리프에 얹는다
CREATE INDEX idx_member_email ON member (email) INCLUDE (created_at);

-- 이 쿼리는 힙(테이블) 접근 0회
SELECT created_at FROM member WHERE email = ?;

INCLUDE 칼럼은 정렬 키가 아니라 리프에 얹히기만 한다. 그래서 인덱스 크기를 덜 키우면서 커버링을 얻는다.

엔진별 차이

  • InnoDB — 보조 인덱스 리프에 PK가 이미 들어 있다

    • SELECT 하는 게 PK뿐이면 자연스럽게 커버링
  • PostgreSQL — index-only scan 은 '가시성 맵'까지 확인해야 한다

    • VACUUM 이 잘 돈 테이블에서 잘 통하고,
      • 죽은 튜플이 많으면 결국 힙을 보러 간다

두 번째가 중요하다 — PostgreSQL에서 커버링이 기대만큼 안 나오면 VACUUM 상태를 먼저 의심한다.

면접 함정

  • SELECT * 인데 커버링 → 모든 칼럼이 인덱스에 없으면 성립하지 않는다. 커버링을 노린다면 select 목록부터 좁혀야 한다.
  • "칼럼을 다 넣으면 항상 이득" → 인덱스가 커지면 캐시 효율과 쓰기 성능이 나빠진다. 자주 도는 쿼리에만 적용한다.

계획에서 확인하기

-- PostgreSQL
EXPLAIN (ANALYZE, BUFFERS)
SELECT created_at FROM member WHERE email = 'a@b.com';
-- Index Only Scan using idx_member_email on member
--   Heap Fetches: 0            ← 0 이면 완전한 커버링
--   Heap Fetches: 1523         ← 0이 아니면 가시성 맵이 낡은 것 → VACUUM 필요

Heap Fetches가 핵심 지표다. index-only scan으로 잡혔는데도 이 값이 크면, 인덱스 문제가 아니라 VACUUM이 안 돌아 가시성 맵이 낡은 것이다. 인덱스를 고치기 전에 이걸 먼저 본다.

VACUUM (ANALYZE) member;    -- 가시성 맵 갱신 → Heap Fetches 가 0으로 떨어진다

어디까지 넣을 것인가

자주 도는 목록 쿼리의 SELECT 칼럼이 3~4개로 고정 → 커버링을 노릴 만하다

  • SELECT 칼럼이 매번 다르거나 10개가 넘는다 — → 인덱스만 커지고 이득이 없다

판단 기준은 인덱스가 커져서 잃는 것(캐시 효율·쓰기 비용)보다 힙 접근을 없애서 얻는 것이 큰가이다. 조건에 맞는 행이 많은 쿼리(수백~수천 행)일수록 이득이 크다 — 힙 랜덤 접근이 그만큼 줄기 때문이다.

함께 보면 좋은 용어

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