EXPLAIN 실행 계획
핵심 개념
DB가 쿼리를 어떻게 실행할지 보여주는 분석 도구.
EXPLAIN SELECT * FROM orders
WHERE user_id = 123
ORDER BY created_at DESC;
EXPLAIN 결과 읽기 (MySQL)
+----+------+-------+------+------+----------+
| id | type | table | key | rows | Extra |
+----+------+-------+------+------+----------+
| 1 | ref | orders| idx | 15 | Using .. |
+----+------+-------+------+------+----------+
type 컬럼 (접근 방식, 성능순)
| type | 의미 | 성능 |
|---|
| const | PK/유니크 1건 | 최고 |
| ref | 인덱스 동등 조건 | 좋음 |
| range | 인덱스 범위 스캔 | 보통 |
| index | 인덱스 풀 스캔 | 나쁨 |
| ALL | 테이블 풀 스캔 | 최악 |
Extra 컬럼의 핵심 키워드
Using index → 커버링 인덱스 (좋음!)
Using where → WHERE 필터링 (보통)
Using filesort → 정렬 추가 수행 (주의)
Using temporary → 임시 테이블 생성 (주의)
Using index condition → 인덱스 컨디션 푸시다운
실무 튜닝 예시
-- 느린 쿼리
EXPLAIN SELECT * FROM orders
WHERE status = 'pending'
ORDER BY created_at;
-- type: ALL, Extra: Using filesort ← 풀스캔 + 정렬!
-- 인덱스 추가 후
CREATE INDEX idx_status_date
ON orders (status, created_at);
EXPLAIN SELECT * FROM orders
WHERE status = 'pending'
ORDER BY created_at;
-- type: ref, Extra: Using index condition ← 개선!
PostgreSQL EXPLAIN 차이
-- PostgreSQL은 비용 정보 제공
EXPLAIN ANALYZE SELECT ...
-- 결과 예시
Seq Scan on orders
(cost=0.00..1234.00 rows=100 width=52)
(actual time=0.5..15.2 rows=98 loops=1)
| 항목 | 의미 |
|---|
| cost | 예상 비용 (상대값) |
| rows | 예상 행 수 |
| actual time | 실제 실행 시간 |
| ANALYZE | 실제 실행하여 측정 |
Q.EXPLAIN 결과에서 type이 ALL이면 어떤 의미이고, 어떻게 개선하나요?
풀 테이블 스캔입니다. 인덱스를 쓰지 못하고 모든 행을 읽습니다.
원인부터 확인합니다.
| 원인 | 조치 |
|---|
| 조건 컬럼에 인덱스가 없다 | 인덱스를 만든다 |
| 컬럼에 함수를 씌웠다 | 범위 조건으로 다시 쓴다 |
| 타입이 다르다 | 값의 타입을 맞춘다 |
| 복합 인덱스의 선행 컬럼이 없다 | 순서를 바꾸거나 다른 인덱스를 만든다 |
| 조건이 대부분의 행에 해당한다 | 정상이다. 풀 스캔이 더 빠르다 |
마지막 경우가 중요합니다. 결과가 전체의 30%를 넘으면 옵티마이저가 일부러 풀 스캔을 고릅니다. 인덱스로 하나씩 찾아 테이블을 왕복하는 것보다 순차로 다 읽는 것이 빠르기 때문입니다.
흔한 실수: ALL 을 보면 무조건 인덱스를 추가하는 것. 작은 테이블이거나 대부분을 읽는 쿼리면 인덱스가 오히려 느립니다.
Q.Using filesort와 Using temporary는 각각 어떤 상황에서 발생하나요?
| 표시 | 언제 | 뜻 |
|---|
| Using filesort | ORDER BY 를 인덱스로 해결하지 못할 때 | 결과를 따로 정렬한다 |
| Using temporary | GROUP BY, DISTINCT, 일부 UNION | 중간 결과를 임시 테이블에 담는다 |
이름이 오해를 부릅니다. filesort 는 반드시 파일을 쓰는 것이 아니라, 정렬 버퍼로 끝나면 메모리에서 처리합니다. 결과가 크면 디스크를 씁니다.
해결 방향입니다.
| 상황 | 조치 |
|---|
| ORDER BY 컬럼이 인덱스에 없다 | 정렬 컬럼까지 포함한 복합 인덱스 |
| WHERE 와 ORDER BY 컬럼이 다른 인덱스 | 하나의 복합 인덱스로 합친다 |
| GROUP BY 가 인덱스 순서와 다르다 | 인덱스 순서를 맞춘다 |
| 정렬 대상이 너무 많다 | 먼저 걸러내거나 커서 페이지네이션으로 바꾼다 |
흔한 실수: 둘 다 무조건 나쁘다고 보는 것. 결과가 수십 행이면 정렬 비용이 미미합니다. 수십만 행을 정렬할 때 문제가 됩니다.
Q.느린 쿼리를 발견했을 때 어떤 순서로 분석하나요?
| 순서 | 하는 일 |
|---|
| 1 | 실제로 느린지 확인한다. 평균인지 특정 조건에서만인지 |
| 2 | 실행 계획을 본다. 스캔 방식, 읽은 행 수, 정렬과 임시 테이블 |
| 3 | 예상과 실제를 비교한다. 크게 다르면 통계가 오래된 것이다 |
| 4 | 인덱스로 풀리는지 본다. 없거나, 있는데 못 타는지 |
| 5 | 쿼리를 고친다. 함수 제거, 조인 순서, 불필요한 컬럼 제거 |
| 6 | 그래도 안 되면 구조를 본다. 파티셔닝, 캐시, 반정규화 |
3번이 자주 빠집니다. 옵티마이저가 예상한 행 수와 실제가 수백 배 다르면 잘못된 계획을 고르고, 원인은 통계가 낡은 것입니다. 통계 갱신만으로 해결되는 경우가 있습니다.
같은 쿼리라도 파라미터에 따라 계획이 달라질 수 있으므로, 문제가 된 실제 값으로 확인해야 합니다.
흔한 실수: 계획만 보고 실제 실행을 안 해보는 것. 예상 비용과 실제 소요는 다릅니다. 실측 옵션으로 실제 행 수와 시간을 함께 봐야 합니다.
먼저 스스로 답해보고 아래 답변과 견줘보세요. 막히는 부분은 문제로 확인할 수 있어요.
읽었으면 문제로 확인해보세요
데이터베이스 문제를 풀면 틀린 문제가 자동으로 노트에 쌓입니다. 가입 없이 5문제를 먼저 풀어볼 수도 있어요.