MySQL 8.x 기준으로 작성되었습니다. 일부 항목은 버전에 따라 동작 방식이 다를 수 있습니다.
| 우선순위 | Extra 키워드 | 의미 | 성능 관점 해석 | 튜닝 포인트 |
|---|---|---|---|---|
| 1 | Using filesort | ORDER BY를 인덱스로 처리하지 못하고 별도 정렬 수행 | 대량 데이터에서 매우 느림 | (WHERE 조건 컬럼, ORDER BY 컬럼) 순서로 복합 인덱스 생성 |
| 2 | Using temporary | 임시 테이블 생성 (주로 GROUP BY, DISTINCT) | 메모리/디스크 사용 → 성능 저하 | GROUP BY 컬럼 인덱스 검토 |
| 3 | Using where | 스토리지 엔진에서 조건 필터링 수행 | 정상적이지만 인덱스 미흡 가능성 | 조건 컬럼 인덱스 여부 점검 |
| 4 | Using index | 인덱스만으로 결과 반환 (Covering Index) | 매우 빠름 (이상적인 상태) | 유지 또는 확장 고려 |
| 5 | Using index condition | ICP(Index Condition Pushdown) 사용 | 랜덤 I/O 감소, 성능 개선 | 복합 인덱스 활용 여부 점검 |
| 6 | Range checked for each record | 조인 시 매 레코드마다 인덱스 범위 재계산 | 매우 비효율적 | 조인 조건 인덱스 재설계 |
| 7 | Using join buffer (Block Nested Loop / hash join) | 인덱스 없는 조인 → 조인 버퍼 사용 (8.0.18 미만: BNL, 8.0.18 이상: hash join으로 대체) | 데이터 많으면 급격히 느림 | 조인 컬럼 인덱스 필수 |
| 8 | Using intersect / union / sort_union | 여러 인덱스를 병합 사용 | 단일 인덱스보다 느림 | 복합 인덱스 고려 |
| 9 | Distinct | 중복 제거 수행 | 소량 데이터에서는 무해하나 대량 처리 시 주의 | 불필요한 DISTINCT 제거 |
| 10 | Using MRR | Multi Range Read 사용 — 랜덤 I/O를 시퀀셜하게 변환하여 range scan 성능 개선 | 랜덤 I/O 감소 | mrr 옵티마이저 플래그 및 read_rnd_buffer_size로 제어 가능 |
| 11 | Full scan on NULL key | col IN (subquery) 형태에서 서브쿼리가 NULL을 반환할 가능성이 있을 때 NULL 처리를 위해 인덱스 전체 스캔 선택적 수행 |
조건 설계 문제 | 서브쿼리 결과에서 NULL 제거 또는 NOT IN → NOT EXISTS 리팩터링 |
| 12 | Using index for group-by | GROUP BY를 인덱스로 처리 | 매우 좋음 | 인덱스 순서 유지 |
| 13 | Start temporary / End temporary | 임시 테이블 범위 표시 | Using temporary와 동일 계열 | GROUP BY 구조 개선 |
| 14 | Impossible WHERE | WHERE 조건이 항상 false | 쿼리 오류 가능성 | 조건 로직 점검 |
| 15 | No matching min/max row | MIN/MAX 조건 결과 없음 | 문제 아님 | 정상 동작 |
| 16 | Select tables optimized away | 상수 처리로 테이블 접근 생략 | 매우 이상적 | 유지 |
반드시 경계해야 하는 경우
Using filesort
- 이유: ORDER BY를 인덱스로 처리하지 못해 추가 정렬 단계가 발생
- 문제점: 데이터가 늘어날수록 정렬 비용이 급증 (CPU, 메모리/디스크)
- 튜닝:
(WHERE 조건 컬럼, ORDER BY 컬럼)순서의 복합 인덱스 생성 검토 - 실무 판단: 조회 건수가 많거나 페이징 쿼리면 튜닝 대상
Using temporary
- 이유: GROUP BY, DISTINCT 처리 과정에서 임시 테이블 생성
- 문제점: 메모리 초과 시 디스크 임시 테이블로 전환되어 성능 급락
- 실무 판단: 대용량 테이블에서 GROUP BY가 보이면 우선 의심
Range checked for each record
- 이유: 조인 시 매 row마다 인덱스 범위를 재계산
- 문제점: 인덱스를 제대로 활용하지 못하는 비효율적인 조인
- 실무 판단: 조인 조건 또는 인덱스 설계 오류 가능성 높음
Using join buffer (Block Nested Loop / hash join)
- 이유: 조인 컬럼에 인덱스가 없어 조인 버퍼를 사용
- 버전 차이
- MySQL 8.0.18 미만: BNL(Block Nested Loop) 방식으로 동작
- MySQL 8.0.18 이상: 기본적으로 hash join으로 대체됨. Extra에
Using join buffer (hash join)으로 출력. BNL은 hash join 불가 시 폴백으로만 등장
- 문제점: 테이블 크기가 커질수록 성능이 급격히 저하
- 실무 판단: 조인 컬럼 인덱스 추가가 1차 해결책
매우 좋은 신호
Using index
- 이유: 인덱스만으로 결과를 반환 (Covering Index)
- 의미: 테이블 접근 없이 랜덤 I/O 최소화
- 실무 판단: 이상적인 실행계획, 유지 권장
Using index for group-by
- 이유: GROUP BY를 정렬이나 임시 테이블 없이 인덱스로 처리
- 의미: filesort, temporary 모두 회피
- 실무 판단: GROUP BY 최적화의 정석 상태
Select tables optimized away
- 이유: 조건이 상수로 판단되어 테이블 접근 자체를 생략
- 의미: 실행 비용이 거의 없음
- 실무 판단: 추가 튜닝 불필요
맥락에 따라 판단해야 하는 경우
Using where
- 이유: 스토리지 엔진 단계에서 조건 필터링 수행
- 의미: 정상 동작이나 인덱스가 충분하지 않을 수 있음
- 실무 판단:
rows수가 많다면 인덱스 추가 검토
Using index condition
- 이유: Index Condition Pushdown으로 인덱스 단계에서 필터링
- 의미: 테이블 접근 감소로 성능 개선
- 실무 판단: 나쁘지 않으나 Covering Index(Using index)보다는 한 단계 아래
ICP(Index Condition Pushdown) 란?
- MySQL이 인덱스를 읽는 시점에 WHERE 조건 필터링을 함께 수행하는 최적화 기법
- ICP 미적용: 인덱스 읽기 → 테이블 접근 (랜덤 I/O) → WHERE 조건 평가 → 조건 불일치 시 버림
- ICP 적용: 인덱스 읽기 → 인덱스 단계에서 WHERE 조건 평가 → 조건 일치 시에만 테이블 접근
Using MRR
- 이유: Multi Range Read를 통해 랜덤 I/O를 시퀀셜하게 변환하여 range scan 성능 개선
- 의미: 완전한 자동 최적화가 아니며,
mrr옵티마이저 플래그 및read_rnd_buffer_size설정으로 동작이 제어됨 - 실무 판단: 대용량 range scan에서 효과가 뚜렷함. 기본값으로도 대부분 적절히 동작
Full scan on NULL key
- 이유:
col IN (subquery)형태에서 서브쿼리 결과에 NULL이 포함될 가능성이 있을 때, NULL 처리를 위해 인덱스 전체 스캔을 선택적으로 수행 - 의미: NULL 비교 자체가 원인이 아니라, NULL을 포함할 수 있는 서브쿼리 결과가 원인
- 실무 판단:
NOT IN → NOT EXISTS리팩터링 또는 서브쿼리 결과에서 NULL을 명시적으로 제거
Using intersect / union
- 이유: 여러 단일 인덱스를 병합 사용
- 의미: 인덱스를 사용하지만 최적은 아님
- 실무 판단: 조건이 고정적이면 복합 인덱스 고려
한 줄 요약
filesort,temporary→ 설계 재검토join buffer,range checked→ 인덱스 설계 문제using index→ 잘 만든 쿼리using where→rows수 기준으로 판단Full scan on NULL key→ NULL 포함 서브쿼리 구조 점검