VillageSQL is a drop-in replacement for MySQL with extensions.
All examples on this page work on VillageSQL. Install Now →
멘탈 모델
800페이지짜리 책을 생각해 보세요. “TCP/IP”가 언급된 모든 곳을 찾으려면 모든 페이지를 읽을 수 있습니다 — 그것이 전체 테이블 스캔입니다. 아니면 책 뒤의 색인을 확인해서 “TCP/IP — 42, 107, 391페이지”를 찾아 그 페이지로 바로 갈 수 있습니다. 데이터베이스 인덱스가 하는 일이 그것입니다. MySQL의 기본 인덱스 구조는 B-tree(균형 트리)입니다. 값을 정렬된 상태로 유지하여 빠른 조회(이진 탐색), 범위 스캔(BETWEEN, >, <), 정렬된 출력(ORDER BY)을 가능하게 합니다 — 모두 전체 테이블을 읽지 않고 말입니다.
MySQL이 인덱스를 사용하는 방식
쿼리를 실행하면 MySQL의 옵티마이저는 인덱스가 읽어야 할 행을 좁힐 수 있는지 확인합니다. 쿼리가 인덱스된 열로 필터링하거나 정렬할 때 인덱스를 사용합니다.EXPLAIN: MySQL이 실제로 하는 일 보기
EXPLAIN은 MySQL이 쿼리를 어떻게 실행할 계획인지 보여줍니다. key 열은 어떤 인덱스를 사용했는지(없으면 NULL) 알려주고, rows는 검사해야 했던 행 수를 추정합니다.
type: ref는 MySQL이 인덱스 조회를 사용했다는 뜻입니다. type: ALL은 전체 테이블 스캔을 뜻합니다 — 보통 피하려고 하는 것입니다.
복합 인덱스: 열 순서가 중요합니다
복합 인덱스는 여러 열을 포함합니다. 열 순서가 인덱스를 언제 사용할 수 있는지 결정합니다.WHERE user_id = 42— 예, 가장 왼쪽 열WHERE user_id = 42 AND status = 'shipped'— 예, 두 열 모두WHERE status = 'shipped'— 아니요, 가장 왼쪽 열을 건너뜀
(user_id, status)와 (status, user_id)가 서로 다른 용도를 가진 서로 다른 인덱스라는 뜻입니다. 가장 흔한 쿼리 패턴을 중심으로 복합 인덱스를 설계하세요.
커버링 인덱스: 인덱스만으로 쿼리 처리하기
커버링 인덱스는 쿼리에 필요한 모든 열을 포함합니다. MySQL은 실제 행 데이터를 읽지 않고 인덱스만으로 쿼리에 완전히 답할 수 있습니다 — 큰 테이블에서 상당한 속도 향상입니다.EXPLAIN은 Extra: Using index를 보여줍니다. 행 조회가 필요하지 않습니다.
인덱스를 추가하지 말아야 할 때
인덱스가 많다고 항상 좋은 것은 아닙니다. 모든 인덱스에는 비용이 듭니다:- 쓰기 오버헤드 — 모든 INSERT, UPDATE, DELETE가 테이블의 모든 인덱스를 갱신해야 함
- 저장 공간 — 쓰기가 많은 데이터에서는 인덱스가 테이블 자체만큼 커질 수 있음
- 옵티마이저 복잡성 — 인덱스가 너무 많으면 옵티마이저가 가끔 잘못된 것을 고름
- 열의 카디널리티가 높을 때(서로 다른 값이 많을 때) —
user_id,email,order_id - 쿼리가 테이블의 작은 부분만 필터링할 때
- 낮은 카디널리티 — 가능한 값이 3개인
status, 불리언is_active - 전체 스캔이 어차피 빠를 만큼 테이블이 작을 때
- 열에 쓰기가 많고 거의 쿼리되지 않을 때
기본 키는 항상 인덱스입니다
InnoDB 테이블은 기본 키를 중심으로 구성됩니다 — 기본 키는 별도의 인덱스가 아니라 행의 물리적 저장 순서입니다. 이것은 다음을 뜻합니다:- 기본 키 조회는 가능한 가장 빠른 쿼리입니다
- 기본 키 값이 순차적이면 행이 물리적으로 인접합니다
- 무작위 기본 키(UUID v4 같은)는 삽입 시 단편화를 일으킵니다 — MySQL의 UUID를 보세요
흔한 인덱스 실수
인덱스된 열에 함수 사용하기 — 열을 함수로 감싸면 MySQL은 인덱스를 사용할 수 없습니다:%로 시작하면 인덱스가 도움이 되지 않습니다:
OR 조건을 처리하기 위해 여러 단일 열 인덱스를 병합할 수 있을 때도 있지만, 신뢰할 수 없습니다. 큰 테이블에서는 UNION이 더 예측 가능합니다:
누락된 인덱스 찾기
슬로우 쿼리 로그는 시간 임계값을 넘는 쿼리를 기록합니다 — 무엇에 인덱스를 만들지 찾는 좋은 출발점입니다:EXPLAIN을 실행하세요. type: ALL과 수백만 단위의 rows를 찾으세요.
요약
- B-tree 인덱스는 빠른 조회와 범위 스캔을 위해 값을 정렬된 상태로 유지합니다
- MySQL은 쿼리가 선행 인덱스 열로 필터링하거나 정렬할 때 인덱스를 사용합니다
- 복합 인덱스의 열 순서가 중요합니다 — 가장 흔한 쿼리를 중심으로 설계하세요
- 커버링 인덱스가 가장 빠릅니다 — 쿼리가 인덱스만으로 완전히 답해집니다
- 인덱스는 쓰기에 비용이 듭니다. 읽기 이득이 정당화하는 곳에만 추가하세요
- 열을 함수로 감싸면 그 인덱스를 비활성화한 것입니다 — 대신 범위 조건을 사용하세요
함께 보기
- MySQL에서 EXPLAIN 읽기 — 인덱스가 사용되고 있는지 확인하고 MySQL이 왜 그것을 고르는지 이해하기
- MySQL의 커버링 인덱스 — 행 조회 없이 쿼리에 답하는 인덱스
- MySQL 슬로우 쿼리 로그 — 어떤 쿼리에 먼저 인덱스가 필요한지 찾기
- MySQL INFORMATION_SCHEMA — 쿼리로 인덱스 메타데이터와 사용되지 않는 인덱스 찾기
- 내 MySQL 쿼리는 왜 느릴까? — 인덱스가 속한 더 넓은 튜닝 워크플로
- MySQL의 페이지네이션: 키셋 대 OFFSET — 큰 테이블을 인덱스 기반으로 페이지네이션하는 방법