TestForge | Aidevops | 📊 Plogger ✍️ Blog 📚 Docs
plogger

AI DevOps Korea

AI 서비스 개발, 운영, 성능개선을 하나의 루프로 연결합니다

aidevops.kr에서 LLMOps, RAG, AI Agent, 관측성, 평가, 비용-성능 최적화를 실전 운영 관점으로 정리합니다.

MySQL 인덱스 최적화 전략 — EXPLAIN으로 쿼리 분석하기

MySQL 인덱스 최적화 전략 — EXPLAIN으로 쿼리 분석하기 다이어그램
이 글에서 다루는 핵심 흐름, 아키텍처 구조, 주요 판단 포인트를 한눈에 이해할 수 있도록 정리한 그림입니다.
## 그림으로 보는 구조
[WHERE column = value]
        |
        +--> [Index lookup]
        |        |
        |        v
        |    small search range
        |
        +--> [Full table scan]
                 |
                 v
            read every row

인덱스 튜닝의 핵심은 “인덱스를 만들면 빨라진다”가 아니라, 옵티마이저가 작은 탐색 범위로 좁혀 들어갈 수 있느냐입니다. 즉 인덱스는 자료구조 자체보다도, 쿼리가 얼마나 빠르게 후보 집합을 줄일 수 있는지의 문제로 이해하는 편이 훨씬 실무적입니다.

EXPLAIN으로 쿼리 분석

EXPLAIN SELECT * FROM users WHERE email = 'hoon@test.com';
컬럼의미
type접근 방식 (ALL > index > range > ref > const)
key사용된 인덱스
rows예상 스캔 행 수
ExtraUsing index (커버링), Using filesort (정렬 필요)

인덱스 생성

-- 단일 인덱스
CREATE INDEX idx_email ON users(email);

-- 복합 인덱스 (순서가 중요!)
CREATE INDEX idx_status_created ON orders(status, created_at);

-- 유니크 인덱스
CREATE UNIQUE INDEX idx_unique_email ON users(email);

복합 인덱스 설계 원칙

-- ✅ 올바른 순서 — 선택도 높은 컬럼 먼저
CREATE INDEX idx_status_date ON orders(status, order_date);
SELECT * FROM orders WHERE status = 'PAID' AND order_date >= '2026-01-01';

-- ❌ 인덱스 미사용 — 왼쪽 컬럼(status) 없이 검색
SELECT * FROM orders WHERE order_date >= '2026-01-01';

커버링 인덱스

-- 인덱스만으로 쿼리 완성 — 테이블 조회 없음
CREATE INDEX idx_covering ON posts(user_id, title, created_at);
SELECT user_id, title, created_at FROM posts WHERE user_id = 1;
-- Extra: Using index ✅

인덱스가 안 타는 경우

-- ❌ 함수 사용
WHERE YEAR(created_at) = 2026
-- ✅ 범위로 변환
WHERE created_at BETWEEN '2026-01-01' AND '2026-12-31'

-- ❌ LIKE 앞에 와일드카드
WHERE name LIKE '%hoon%'
-- ✅ 뒤에만 와일드카드
WHERE name LIKE 'hoon%'

-- ❌ 형변환
WHERE user_id = '123'   -- user_id가 INT인 경우
-- ✅
WHERE user_id = 123

슬로우 쿼리 로그

SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1;  -- 1초 이상
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';

운영 환경에서 어려워지는 지점

  • MySQL 인덱스 최적화는 기계적으로 인덱스를 추가하는 일이 아니라 workload 선택도와 접근 경로를 이해하는 일이다.
  • 인덱스가 있어도 predicate 순서와 covering이 맞지 않으면 느린 쿼리는 그대로 느릴 수 있다.
  • 쓰기 비중이 높은 시스템은 인덱스 확산으로 크게 고통받을 수 있다.

중요한 아키텍처 결정

  • WHERE, ORDER BY, 조인 형태를 함께 보고 인덱스를 설계한다.
  • 겹치는 단일 목적 인덱스 여러 개보다 잘 맞춘 복합 인덱스 몇 개를 선호한다.
  • EXPLAIN과 운영에 가까운 cardinality로 가정을 검증한다.

실무 예시

인덱스는 옵티마이저가 실제로 활용할 수 있는 쿼리 경로와 맞아야만 가치가 있다.

WHERE tenant_id = ? AND deleted = 0 ORDER BY created_at DESC
-> INDEX (tenant_id, deleted, created_at)

피해야 할 안티패턴

  • 복합 인덱스의 left-prefix 동작을 무시하는 것.
  • 사소한 변형만 있는 중복 인덱스를 계속 추가하는 것.
  • 로컬 테스트 데이터 cardinality가 운영 실행 계획을 예측한다고 믿는 것.

운영 체크리스트

  • slow query log를 정기적으로 본다.
  • 실제 cardinality와 filtered rows를 확인한다.
  • 기능 변경 뒤 중복 인덱스를 정리한다.
  • 인덱스 추가 뒤 쓰기 지연을 측정한다.

최종 판단

MySQL 인덱스 튜닝은 workload 매칭 작업이다. 보통 이기는 전략은 더 많은 인덱스가 아니라 더 적고 더 잘 맞는 인덱스와 더 나은 쿼리 형태다.

Continue Reading

다음으로 읽기 좋은 글

다음 탐색

이 주제를 시스템 관점으로 더 이어서 보기