Skip to Content

타지 않는 인덱스

읽는 양은 어떻게 비용이 되는가

where transaction_no = '...' 를 붙이면 45초가 걸리는데, 조건을 아예 빼면 즉시 나온다. 보통은 반대다 — 조건은 결과를 줄여주니까. 이 모순 뒤에는 “무엇을 얼마나 읽을 것인가”를 60년간 다르게 답해온 자료구조와 스토리지 엔진의 역사가 있다.

이 책은 CREATE INDEX를 쳐봤지만 “그래서 이게 왜 빨라지는 거지?”와 “인덱스를 걸었는데 왜 type: ALL이 그대로지?”에 자신 있게 답하지 못하는 백엔드 개발자를 위해 썼다. 정답을 외우는 대신, 디스크 한 번 읽는 값이 비싸서 트리 모양이 정해지던 시절에서 출발해 — 16KB 페이지라는 단위, 테이블 자체가 인덱스인 클러스터드 구조, 통계가 없을 때 옵티마이저가 꽂는 상수, 그리고 인덱스를 운영 테이블에 실제로 밀어 넣는 절차까지 — 오늘의 실행계획이 왜 그 모양으로 나오는지를 따라간다.

관통하는 한 문장은 이것이다. 인덱스는 빠르게 읽는 기술이 아니라 덜 읽는 기술이고, 그 “덜”의 단위는 행이 아니라 16KB 페이지다.

이 문장은 측정으로 확인된다. 370만 행(테이블 통계 3,705,149) 로그 테이블에서 인덱스 없는 조회는 382만 행(실행계획 rows 추정치)을 읽어 1행을 반환하는 데 45.36초를 썼다. 두 숫자가 다른 것은 오기가 아니다 — 앞은 테이블에 실제로 있는 행 수이고, 뒤는 옵티마이저가 “읽어야 한다”고 추정한 행 수다. 인덱스가 없어 샘플링할 대상이 없으면 이 추정이 실제와 어긋난다. 45.36초 중 41.17초가 순수 테이블 스캔이다. 쿼리를 아무리 다듬어도 이 값은 안 줄어든다 — 읽는 양 자체가 그대로이기 때문이다. (✅ 실측 출처: 조건을 안 걸면 빠릅니다 · 인덱스는 공짜가 아닙니다. 이 책은 여는 글에서 이 장면을 다시 놓고, 2·3부에서 그 41.17초를 페이지 단위로 쪼갠다.)

각 장은 배경 → 스토리 → 핵심 세 걸음으로 나아가고, 개념마다 재현 가능한 SQL과 그 장을 한 컷으로 요약한 삽화가 함께한다. 모든 수치는 MySQL 8.x / InnoDB 기준이며, 독자가 같은 결과를 얻을 수 있도록 시드 스크립트를 함께 둔다.

목차

제1부 · 찾는 법의 역사

  1. 처음부터 끝까지 읽던 시절 — 순차 스캔과 ISAM
  2. B-tree는 왜 이진트리가 아닌가 — 디스크가 정한 모양
  3. 리프를 잇다 — B+tree와 범위 검색
  4. 누가 고를 것인가 — System R과 비용 기반 옵티마이저

제2부 · 페이지라는 단위 5. 모든 것은 16KB다 — 페이지·행·오버플로 6. 테이블은 사실 인덱스다 — 클러스터드 인덱스 7. 왕복 — 세컨더리 인덱스는 PK를 들고 다시 내려간다 8. 무엇이 메모리에 남는가 — 버퍼풀과 Young/Old LRU

제3부 · 왜 안 타는가 9. EXPLAIN 읽는 법 — type·key·rows·filtered 10. filtered=10이라는 상수 — 통계가 없을 때 11. 조용히 느려지는 것들 — 따옴표·함수·앞 와일드카드 12. 일부러 안 타는 경우 — 선택도와 랜덤 접근 비용

제4부 · 청구서 13. 인덱스 개수는 비용의 단위가 아니다 — 단조 증가 키 vs 난수 키 14. 페이지 분할 — 난수 키가 비싼 진짜 이유 15. 체인지 버퍼 — 그럼에도 견디는 이유 16. 커버링 인덱스 — 왕복을 없애는 값

제5부 · 운영에 넣기 17. 온라인 DDL — ALGORITHM과 LOCK이 보장하는 범위 18. MDL이 만드는 큐 — LOCK=NONE은 무중단이 아니다 19. 나아졌다는 걸 어떻게 증명하나 — 전후 측정의 규율 20. 인덱스로 못 푸는 것 — 보관정책·파티셔닝·스키마

닫는 글 — 다시, 읽는 양 앞에서


여는 글부터 읽기 — 조건을 안 걸면 빠릅니다

Last updated on