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 whererows 수 기준으로 판단
  • Full scan on NULL key → NULL 포함 서브쿼리 구조 점검