Explain
contents
EXPLAIN 실행 계획(Execution Plan) 을 읽고 해석하는 방법에 대한 걸 알아보겠습니다.
EXPLAIN의 결과물은 쿼리에 대한 건강 검진 성적표라고 생각하시면 됩니다. 데이터베이스가 당신의 데이터를 찾기 위해 어떤 경로를 이용할지 정확히 보여줍니다. 데이터베이스(MySQL, PostgreSQL, Oracle 등)마다 출력 형식은 조금씩 다르지만, 핵심 개념은 동일합니다.
느린 쿼리를 고치기 위해 반드시 확인해야 할 중요한 지표들을 중심으로 설명하겠습니다.
1. 속도의 계층 구조 (The "Type" Column)
모든 EXPLAIN 계획에서 가장 중요한 컬럼은 보통 type (MySQL) 또는 access method (PostgreSQL)라고 불립니다. 이것은 데이터베이스가 행(Row)을 어떻게 가져오고 있는지를 알려줍니다.
다음은 최고(가장 빠름) 에서 최악(가장 느림) 순서의 계층 구조입니다.
| 순위 | Type (접근 방식) | 설명 | 판정 |
|---|---|---|---|
| 1 | system / const | 테이블에서 정확히 1개의 행만 읽음 (예: WHERE primary_key = 1). |
🚀 즉시 완료 (Instant) |
| 2 | eq_ref | Primary Key를 사용하여 테이블을 조인함. | ✅ 매우 훌륭함 |
| 3 | ref | 고유하지 않은(Non-Unique) 인덱스 사용 (예: WHERE status = 'active'). |
👍 좋음 |
| 4 | range | 인덱스의 특정 범위만 스캔 (예: WHERE date BETWEEN... 또는 id > 50). |
👌 허용 가능 |
| 5 | index | 인덱스 트리 전체를 스캔 (데이터가 아닌 인덱스만 훑음). | ⚠️ 느림 |
| 6 | ALL / Seq Scan | Full Table Scan (전체 테이블 스캔). 디스크에 있는 모든 행을 하나하나 다 읽음. | 🛑 위험 (DANGER) |
경험칙(Rule of Thumb): 데이터가 많은 테이블에서
ALL이나Seq Scan이 보인다면, 인덱스가 누락된 것입니다.
2. "Rows" 컬럼 (추정치)
이 컬럼은 옵티마이저가 결과를 찾기 위해 검사해야 한다고 생각(추정) 하는 행의 개수를 나타냅니다.
- 왜 중요한가:
- 격차(The Gap): 옵티마이저는
rows: 1이라고 예상했는데 실제 쿼리 결과가 10만 건이라면, 통계 정보(Statistics) 가 오래된 것입니다. 옵티마이저가 잘못된 수학 계산을 하고 있다는 뜻입니다. - 승수 효과(The Multiplier): 조인(JOIN)을 할 때 이 숫자는 곱해집니다. 테이블 A에서 100개를 보고 테이블 B에서 100개를 본다면, 엔진은 10,000번(
100 * 100)의 연산을 수행할 수도 있습니다.
- 격차(The Gap): 옵티마이저는
3. "Key" 또는 "Possible Keys" 컬럼
possible_keys: 데이터베이스가 이 쿼리를 위해 사용할 수도 있는 인덱스 후보들입니다.key(또는Index Cond): 데이터베이스가 실제로 사용하기로 결정한 인덱스입니다.
문제 해결 팁:
possible_keys에는 인덱스가 있는데key가NULL이라면? 데이터베이스가 인덱스를 타는 것보다 그냥 테이블 전체를 훑는 게 더 빠르다고 판단한 것입니다 (데이터가 아주 적은 테이블에서 흔함).- 둘 다
NULL이라면? 관련된 인덱스가 아예 없다는 뜻입니다.
4. "Extra" 컬럼 (MySQL 특화)
이 컬럼은 숨겨진 비용을 보여주며, 성능 문제의 원인이 자주 발견되는 곳입니다.
Using index(좋음): "커버링 인덱스(Covering Index)"입니다. 요청한 데이터가 인덱스 트리 안에 전부 있어서, 실제 데이터 테이블을 쳐다볼 필요조차 없었다는 뜻입니다.Using where(보통): 데이터를 가져온 후WHERE절을 사용해 필터링했다는 뜻입니다.Using temporary(나쁨): 중간 결과를 담기 위해 임시 테이블을 생성했습니다 (GROUP BY에서 흔함). 종종 디스크 쓰기를 유발합니다.Using filesort(나쁨): 인덱스를 사용해 정렬할 수 없어서, 별도의 정렬 알고리즘을 수행했습니다 (ORDER BY시 발생, 종종 디스크 사용).
5. 실제 예시 (Real-World Example)
쿼리:
SELECT * FROM Users WHERE email = 'test@example.com';
시나리오 A: 나쁜 계획 (The Bad Plan)
id | select_type | table | type | possible_keys | key | rows | Extra
1 | SIMPLE | Users | ALL | NULL | NULL | 100000 | Using where
- 진단:
type: ALL은 이메일 하나를 찾기 위해 10만 줄을 다 읽었다는 뜻입니다.key: NULL은 인덱스가 없음을 의미합니다. - 해결:
email컬럼에 인덱스를 생성해야 합니다.
시나리오 B: 좋은 계획 (인덱스 생성 후)
id | select_type | table | type | possible_keys | key | rows | Extra
1 | SIMPLE | Users | const | idx_email | idx_email| 1 | NULL
- 진단:
type: const는 해당 데이터로 바로 점프했음을 의미합니다.rows: 1은 거의 일을 하지 않고 찾았다는 뜻입니다.
6. PostgreSQL의 특징: EXPLAIN ANALYZE
PostgreSQL에서 EXPLAIN만 쓰면 단순 추정치만 보여줍니다.
EXPLAIN ANALYZE를 실행하면, 데이터베이스가 실제로 쿼리를 실행한 뒤 추정치와 실제 소요 시간을 비교해 줍니다.
Seq Scan on users (cost=0.00..183.00 rows=50 width=128) (actual time=0.015..2.450 rows=50 loops=1)
cost=0.00..183.00: 옵티마이저가 계산한 수학적 점수 (낮을수록 좋음).actual time=...: 실제로 걸린 시간. 만약actual time은 엄청 긴데cost는 낮다면, 옵티마이저가 상황을 잘못 파악하고 있는 것입니다 (보통 디스크 I/O 지연 같은 외부 요인).
요약 체크리스트
- Type 확인:
ALL인가요? (인덱스 추가 필요). - Key 확인: 예상한 인덱스를 사용하고 있나요?
- Rows 확인: 숫자가 비상식적으로 높은가요?
- Extra 확인:
Using filesort나Using temporary가 보이나요? (ORDER BY나GROUP BY최적화 필요).
references