EXPLAIN 결과 칼럼
MySQL의 EXPLAIN
MySQL의 EXPLAIN
아래는 MySQL의 EXPLAIN(또는 MySQL 8.0.18 이상에서 사용 가능한 EXPLAIN ANALYZE) 결과로 나오는 각 컬럼이 가질 수 있는 대표적인 값(종류)들을 정리한 것입니다. 책 10.3장 목차에 따라, 순서대로 알아보겠습니다.
EXPLAIN 결과 칼럼의 종류
1. id 칼럼
- 쿼리 내에서 “각 SELECT 블록”에 부여되는 식별자(ID)입니다.
- 숫자가 클수록 상위(outer) SELECT, 작을수록 하위(subquery/derived) SELECT을 의미합니다.
실행 순서는 이
id값을 기준으로 내림차순(큰 것부터 작은 것 순)으로 처리됩니다.대표적인 값
1, 2, 3, … (정수)
- 단일 쿼리(서브쿼리 없는 SIMPLE)인 경우 항상
1만 나옵니다. 서브쿼리가 있으면, 예를 들어
SELECT * FROM ( SELECT a, b FROM T1 ) AS sub JOIN T2 ON sub.a = T2.c;위와 같은 경우 내부 서브쿼리(derived table)는
id = 2, 바깥쪽 PRIMARY SELECT는id = 1과 같이 번호가 매겨집니다.
- 단일 쿼리(서브쿼리 없는 SIMPLE)인 경우 항상
NULL
- (드물게) 버전이나 상황에 따라 표시되지 않는 시스템 테이블 또는 특정 옵티마이저 단계에서
NULL로 보일 수 있으나, 일반적으로 숫자값이 뜹니다.
- (드물게) 버전이나 상황에 따라 표시되지 않는 시스템 테이블 또는 특정 옵티마이저 단계에서
2. select_type 칼럼
- 해당
id가 “쿼리 전체 중 어떤 성격의 SELECT 블록인가”를 알려줍니다. 총 7~8개의 주요 타입이 있으며, 쿼리 구조(서브쿼리, UNION 등)에 따라 달라집니다.
대표적인 값(종류)
SIMPLE
- “서브쿼리 없이 단순 SELECT”일 때 사용됩니다.
- 즉, 모든 테이블을 조인하되 서브쿼리(
(...))가 전혀 없으면SIMPLE만 뜹니다.
PRIMARY
- 전체 실행계획 중 가장 바깥 레벨(맨 바깥, Root)의 SELECT 문입니다.
- 서브쿼리가 하나라도 있으면, 바깥쪽은
PRIMARY로 표시되고, 서브쿼리들은 다른 타입(아래 3~6번)으로 표시됩니다.
SUBQUERY
- FROM, WHERE, SELECT 절 등에서 사용된 “비의존적(subquery) 서브쿼리”일 때 사용됩니다.
- “독립적으로 먼저 실행 후, 결과를 가지고 바깥쪽을 실행”하는 서브쿼리입니다.
DEPENDENT SUBQUERY
- “외부 쿼리의 컬럼값에 의존하는(subquery)” 형태일 때 쓰입니다.
예:
SELECT t1.* FROM T1 AS t1 WHERE t1.id IN ( SELECT t2.ref_id FROM T2 AS t2 WHERE t2.col = t1.col -- t1의 값을 참조 );위처럼 내부 서브쿼리가 외부 쿼리(
t1.col)에 **종속(dependent)**되어 있으면DEPENDENT SUBQUERY로 표시됩니다.
DERIVED
- “FROM절에 있는 서브쿼리(derived table)”입니다.
예:
SELECT x.a, x.b FROM ( SELECT a, b FROM T3 WHERE ... ) AS x JOIN T4 ON x.a = T4.c;여기서
FROM (...) AS x안 부분이DERIVED로 나옵니다.
UNCACHEABLE SUBQUERY (MySQL 8.0.23 이후부터 등장)
- 옵티마이저가 내부적으로 캐시(메모이제이션)하지 못하는 서브쿼리입니다.
UNION
- 여러 SELECT를
UNION으로 합치는 UNION 블록 안의 각각의 SELECT에 붙습니다.
- 여러 SELECT를
UNION RESULT
UNION으로 합친 후 최종 결과를 처리하는 단계에 붙습니다.
3. table 칼럼
- 해당 단계에서 접근하고 있는 “테이블 또는 자료원”의 이름을 표시합니다.
실제 물리 테이블명뿐만 아니라, “임시 테이블(derived)”, “상수 테이블(const)”, “시스템 테이블(system)” 등도 나올 수 있습니다.
대표적인 값(종류)
실제 테이블명
- 예:
users,orders,T1,employee등 사용자가 생성한 물리 테이블 이름.
- 예:
const
- 테이블이 정확히 1행만 가진다고 옵티마이저가 판단될 때(예:
PRIMARY KEY = 상수조회) - 즉, 해당 쿼리가 “코스트(cost)가 거의 0”이라고 판단되면
const로 표시되며, 메모리 내에서 상수화한 단일 행으로 간주됩니다.
- 테이블이 정확히 1행만 가진다고 옵티마이저가 판단될 때(예:
system
- 테이블의 크기가 1행뿐인, 극도로 작은 테이블(예: MySQL이 내부적으로 관리하는 메타 테이블)일 때 사용됩니다.
derived
- FROM절에서 서브쿼리를 통해 만들어진 “임시 테이블(derived table)”을 나타냅니다.
- 예:
FROM (SELECT …) AS sub구문의 derived 결과.
**
** (또는 `dep_subq`) - WHERE, SELECT 절 등에서 실행되는 “non-derived 서브쿼리”를 메모리 내 일시 테이블로 만들 때 이 이름을 사용합니다.
**
**, ** ** - UNION 블록 내부에서 “각각의 SELECT → 합쳐진 결과”를 임시로 처리할 때 사용됩니다.
NULL
- (드물게) 내부 옵티마이저 단계에서 어떤 물리 테이블도 접근하지 않을 때
NULL로 표시됩니다.
- (드물게) 내부 옵티마이저 단계에서 어떤 물리 테이블도 접근하지 않을 때
4. partitions 칼럼
- 파티셔닝(partitioned table)을 사용 중이라면, 이 단계에서 액세스하는 “파티션 이름(PARTITION)” 목록을 보여줍니다.
파티션이 지정되지 않았거나, 파티셔닝 하지 않은 테이블이라면
NULL이 뜹니다.대표적인 값(종류)
파티션 이름 리스트(콤마 구분)
- 예:
p0,p1,p2 - 또는
p2024_01,p2024_02같은 실제 파티션 이름.
- 예:
NULL
- 파티셔닝을 사용하지 않았거나, 옵티마이저가 파티션 병합 혹은 전체 스캔으로 판단해 “특정 파티션만 접근하지 않는 경우”.
5. type 칼럼 (조인 타입/접근 방식)
- MySQL 옵티마이저가 해당 테이블을 “어떤 방식으로 접근(스캔)하는지”를 나타냅니다.
성능 순서는 아래 예시 순서대로 좋음 → 나쁨(왼쪽이 좋고, 오른쪽이 나쁨):
system < const < eq_ref < ref < fulltext < ref_or_null < index_merge < unique_subquery < index_subquery < range < index < ALL대표적인 값(종류)
system
- 해당 테이블(또는 파티션)에 행이 오직 1개뿐이라 내부적으로 ‘system catalog’ 테이블처럼 처리할 때.
const
- 상수 기반 조회(프라이머리 키 또는 유니크 키 = 상수)로 “최대 1행”을 읽을 때.
eq_ref
- 조인 시, “한 행당 딱 하나의 매칭 행만 찾아올 수 있는(= =)” 방식.
- 보통 PK/FK로 조인하거나 유니크 인덱스 1건 조회일 때.
ref
- “비-유니크 인덱스를 사용해, 일치하는 모든 행을 가져올 가능성”이 있을 때.
- 예:
WHERE non_unique_col = constant형태.
fulltext
- Fulltext 인덱스 검색 시 사용됩니다.
ref_or_null
WHERE col = 상수 OR col IS NULL같이 NULL까지 매칭해야 할 때.
index_merge
- 여러 인덱스를 동시에 사용해 결과를 합치는 방식.
- 예:
(idx_a(a), idx_b(b))두 개의 인덱스 스캔 후 교집합(Intersection)해서 결과를 내는 경우.
unique_subquery
- 인덱스 조건으로 서브쿼리를 하나씩 조인할 때, 내부적으로 비슷한 “인덱스-서브쿼리” 방식이 쓰입니다.
index_subquery
unique_subquery와 유사하지만, 유니크 제약이 없는 인덱스를 쓸 때.
range
- 범위 스캔(범위 조건)으로 “인덱스의 일부 구간(A > 100, B BETWEEN 10 AND 20 등)”을 연속으로 탐색할 때.
index
- 인덱스 파일(리프 노드) 전체를 순차 스캔하는 방식.
- 예: 커버링 인덱스(covering index)를 읽을 때, ORDER BY나 GROUP BY가 인덱스 순서와 맞아떨어질 경우.
ALL
- 테이블 전체를 풀 스캔(Full Table Scan)하는 방식.
- 가장 비용이 많이 드는 접근 방식으로, 가능한 한 피해야 합니다.
6. possible_keys 칼럼
- 옵티마이저가 “이 테이블에 대해 사용할 수 있다고 판단한(후보가 된) 인덱스 목록”을 콤마로 나열합니다.
실제로 그 조건에 맞는 모든 인덱스를 나열하며, 만약 후보가 없으면
NULL로 표시됩니다.대표적인 값(종류)
인덱스 이름 목록(콤마 구분)
- 예:
idx_userid,idx_created_at - 위 예시라면,
WHERE userid = ... AND created_at > ...형태에서 두 인덱스가 후보로 떠 있음을 뜻합니다.
- 예:
NULL
- 이 테이블을 위해 “사용 가능한 인덱스 후보가 전혀 없다”고 판단되는 경우.
- 예: 조건절에 인덱스 컬럼이 하나도 걸리지 않았거나, 함수 (
YEAR(col) = ...)처럼 인덱스가 무력화된 경우.
7. key 칼럼
possible_keys중에서 실제로 “선택되어서 사용된 인덱스” 이름을 보여줍니다.만약 옵티마이저가 “인덱스를 사용하는 것이 오히려 비효율적”이라고 판단하면
NULL로 나타나며, 이때는 풀 스캔(ALL) 같은 방식이 선택됩니다.대표적인 값(종류)
인덱스 이름
- 예:
PRIMARY(프라이머리 키),idx_userid,idx_date등
- 예:
NULL
- 실제 사용된 인덱스가 없어서, 테이블 풀 스캔(ALL) 또는 다른 방식(예: range→인덱스를 활용 못 함)으로 진행하는 경우.
8. key_len 칼럼
- 실제로 사용된 인덱스(
key)의 바이트(byte) 길이를 나타냅니다. - 인덱스에 걸린 컬럼 데이터 타입과 순서에 따라 바이트 수가 달라집니다.
숫자형은 4~8바이트, 문자열(VARCHAR)이나 CHAR는 그 길이에 따라 달라집니다.
대표적인 값(종류)
정수값(n)
- 예:
4,5,8,9등 - 예를 들어,
INT단일 컬럼 인덱스만 썼다면4(4바이트),(col1(INT), col2(INT))조합 인덱스를 썼다면8(4바이트+4바이트) 등이 나옵니다. - 문자형
VARCHAR(10)이 UTF-8 3바이트 문자로 인덱싱되면 최대30(10×3) 등으로 계산됩니다.
- 예:
NULL
- 인덱스를 사용하지 않거나, 정보가 표시되지 않는 경우(예: FULLTEXT 스캔 시).
9. ref 칼럼
- 인덱스가 사용될 때, 그 인덱스 어떤 값에 매칭해서 접근했는지를 보여줍니다.
보통 “컬럼명” 또는 “상수(const)” 형태로 표시되고, 여러 값을 사용하는 경우
const,const처럼 콤마로 구분됩니다.대표적인 값(종류)
상수 값 또는 컬럼명
예:
constWHERE id = 123같은 경우, “id 컬럼을 상수 123과 비교”하므로ref = const로 표시됩니다.
예:
T1.col_name- 조인 시 “다른 테이블의 컬럼”과 비교하여 인덱스를 타는 경우,
ON T1.id = T2.id라면ref = T2.id처럼 나옵니다.
- 조인 시 “다른 테이블의 컬럼”과 비교하여 인덱스를 타는 경우,
복합(콤마 구분)
- 여러 컬럼이 결합된 인덱스를 쓸 때, 예를 들어
(col1, col2)인덱스를WHERE col1 = 1 AND col2 = 2로 사용한다면ref = const,const또는ref = ‘1’,‘2’처럼 표시됩니다.
- 여러 컬럼이 결합된 인덱스를 쓸 때, 예를 들어
NULL
- 인덱스를 사용하지 않거나,
ALL(풀 스캔)처럼 인덱스 외 방법을 쓸 때.
- 인덱스를 사용하지 않거나,
10. rows 칼럼
설명
- 옵티마이저가 추정한(estimated) “이 단계에서 스캔해야 할(읽어야 할) 행(row) 수”입니다.
- 실제 실행 시 읽은 행 수가 궁금하면 MySQL 8.0.18+버전에서
EXPLAIN ANALYZE를 사용해야 합니다.
대표적인 값(종류)
정수값(n)
- 예:
1,10,1000,50000등 - 인덱스를 잘 탄다면
rows가1~10정도로 보여지고, 풀 스캔이면 몇만 건 이상으로 표시될 수 있습니다.
- 예:
NULL
- 이 단계에서 읽을 행 수를 추정할 수 없거나, 드문 경우에 표기되지 않을 때(예: 특정 내부 임시 테이블 단계).
11. filtered 칼럼
- 해당 단계에서 “조건절(WHERE)이 얼마나 걸러내는지(percentage)”를 추정한 값입니다.
값은 0.00~100.00 사이의 소수점 퍼센트이며, 예를 들어
filtered = 50.00이면 “추정 상 이 단계에서 50%의 행만 다음 단계로 넘길 것”이라는 의미입니다.대표적인 값(종류)
0.00 ~ 100.00 (소수점)
- 예:
100.00(조건절이 없어서 모든 행을 넘긴다고 추정하거나, 인덱스가 커버링되었을 때) - 예:
10.00(조건절에서 10%만 다음 단계로 남는다)
- 예:
NULL
- “조건 절이 없어서(filtered 개념 자체가 없는 단계)”인 경우
- 내부 파티션 단계 등에서 표시되지 않을 때.
12. Extra 칼럼
- 옵티마이저가 실행 계획에 대해 추가로 알려주는 “특이사항”을 나타냅니다.
복수의 메시지를 띄울 수 있으며, 콤마(또는 스페이스)로 구분됩니다.
대표적인 값(종류)
Using where
- 이 단계에서 “WHERE 조건을 평가해서 필터링”을 수행함을 의미합니다.
Using index
- “커버링 인덱스(Covering Index)”만으로 결과를 반환하므로, 추가로 테이블(레코드)을 읽지 않음을 의미합니다.
Using index condition
- 인덱스 조건 푸시다운(Index Condition Pushdown)을 통해, 인덱스 레벨에서 일부 WHERE 절을 처리할 때 표시됩니다.
Using temporary
- 결과를 정렬·그룹화하기 위해 “임시 테이블(Temporary Table)”을 메모리/디스크에 생성함을 의미합니다.
Using filesort
- ORDER BY나 GROUP BY를 수행할 때, “인덱스를 사용한 정렬이 불가능해 임시로 파일 또는 메모리 정렬”(filesort)을 해야 함을 의미합니다.
Using join buffer (Block Nested-Loop)
- 조인 시 BNL(Block Nested Loop) 방식으로 처리하며, 메모리 상에 join buffer를 만들어 사용함을 뜻합니다.
Impossible WHERE
- WHERE 조건이 “절대 참(true)이 될 수 없는 식”으로 옵티마이저가 판단했을 때 예외적으로 표시됩니다.
No matching index
- 조인을 수행할 때 “조인 조건에 사용할 수 있는 인덱스가 전혀 없어서, 풀 스캔(all)만 가능”하다는 의미입니다.
Distinct
SELECT DISTINCT …를 수행하는 단계임을 나타냅니다.
Select tables optimized away
- 아무 테이블도 액세스할 필요 없이(예:
SELECT 1+1) 바로 상수 반환만 하면 되는 경우.
- 아무 테이블도 액세스할 필요 없이(예:
Metadata Lock
- DDL/DDL lock이 있는 상태에서, 메타데이터 락(MD locking) 관련 메시지가 나옵니다.
Using MRR (Multi-Range Read)
- InnoDB 스토리지 엔진이 “multi-range read” 최적화를 사용했을 때 표시됩니다.
Using union(derived)
- Derived table(파생 테이블)을 union 형태로 최적화했음을 의미합니다.
Materialized (MySQL 8.0 이후)
- CTE(Common Table Expressions, WITH문)를 Materialize 했음을 의미합니다.
Ref access on null key
- 조인 시
NULL을 키로 접근했다는 뜻(예:WHERE col IS NULL인덱스 범위 접근).
- 조인 시