EXPLAIN은 옵티마이저의 **계획(추정)**을 보여주지만, 그 계획이 실제로 빠른지는 알려주지 않습니다. EXPLAIN ANALYZE(MySQL 8.0.18+)는 쿼리를 실제로 실행한 뒤 각 단계에 걸린 시간과 처리한 행 수를 돌려줍니다. 추정과 실제가 얼마나 어긋났는지가 병목의 핵심 단서입니다.
SQL
EXPLAIN ANALYZE
SELECT * FROM orders WHERE user_id = 42 AND status = 'paid';
출력에서 읽는 4가지 수치
각 노드는 (cost=... rows=...) (actual time=A..B rows=R loops=L) 형태로 나옵니다. 앞은 추정, 뒤 actual이 실제 측정값입니다.
| 수치 | 의미 | 보는 법 |
|---|---|---|
actual time=A..B | A=첫 행까지 ms, B=마지막 행까지 ms | B가 큰 노드가 실제 병목 |
rows=R | 실제 반환 행 수 | 추정 rows와 크게 다르면 통계 부정확 |
loops=L | 이 노드가 반복 실행된 횟수 | 조인 안쪽에서 loops가 크면 비용 폭증 |
actual time 합산 | 자식 노드 시간 포함 | 부모-자식 시간 차이가 그 단계 순수 비용 |
핵심은 추정 rows와 실제 rows의 괴리입니다. 옵티마이저가 100행을 예상했는데 실제 50만 행이면, 잘못된 통계로 엉뚱한 실행계획을 골랐다는 신호입니다.
실제 실행시간으로 병목 찾기
- 출력의 가장 안쪽(들여쓰기 깊은) 노드부터 읽습니다. 실행은 안에서 밖으로 진행됩니다.
actual time=...B값이 가장 큰 노드를 찾습니다. 그 노드가 전체 시간을 잡아먹는 곳입니다.- 그 노드에서
loops가 1보다 크면, 실제 단일 비용은B이지만 총 비용은 대략B × loops입니다. 중첩 루프 조인의 안쪽이 흔한 범인입니다. - 추정
rows와 실제rows차이가 10배 이상이면ANALYZE TABLE <테이블>로 통계를 갱신합니다. Table scan on orders같은 노드가 실제 시간 대부분을 차지하면, 인덱스 부재가 원인입니다.
SQL
-- 통계가 어긋났을 때
ANALYZE TABLE orders;
-- 다시 측정
EXPLAIN ANALYZE SELECT ...;
EXPLAIN ANALYZE는 쿼리를 실제로 실행하므로 UPDATE/DELETE에는 신중히 써야 합니다(트랜잭션 내에서 롤백 권장).
요점 정리
EXPLAIN ANALYZE는 추정이 아닌 실측 시간·행 수를 준다.- 들여쓰기 깊은 노드부터,
actual time끝값이 큰 곳이 병목. - 추정
rows와 실제rows가 크게 다르면 통계를ANALYZE TABLE로 갱신. loops가 큰 조인 안쪽 노드는 단일 비용이 작아도 총합이 폭증한다.
실행계획을 직접 측정하고 인덱스 전후의 actual time 변화를 비교하는 실습은 데이터베이스 트랙에서 회원가입 없이 무료로 할 수 있습니다.