Post

EXPLAIN 결과 칼럼

MySQL의 EXPLAIN

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. 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과 같이 번호가 매겨집니다.

    2. NULL

      • (드물게) 버전이나 상황에 따라 표시되지 않는 시스템 테이블 또는 특정 옵티마이저 단계에서 NULL로 보일 수 있으나, 일반적으로 숫자값이 뜹니다.

2. select_type 칼럼

  • 해당 id가 “쿼리 전체 중 어떤 성격의 SELECT 블록인가”를 알려줍니다.
  • 총 7~8개의 주요 타입이 있으며, 쿼리 구조(서브쿼리, UNION 등)에 따라 달라집니다.

  • 대표적인 값(종류)

    1. SIMPLE

      • “서브쿼리 없이 단순 SELECT”일 때 사용됩니다.
      • 즉, 모든 테이블을 조인하되 서브쿼리((...))가 전혀 없으면 SIMPLE만 뜹니다.
    2. PRIMARY

      • 전체 실행계획 중 가장 바깥 레벨(맨 바깥, Root)의 SELECT 문입니다.
      • 서브쿼리가 하나라도 있으면, 바깥쪽은 PRIMARY로 표시되고, 서브쿼리들은 다른 타입(아래 3~6번)으로 표시됩니다.
    3. SUBQUERY

      • FROM, WHERE, SELECT 절 등에서 사용된 “비의존적(subquery) 서브쿼리”일 때 사용됩니다.
      • “독립적으로 먼저 실행 후, 결과를 가지고 바깥쪽을 실행”하는 서브쿼리입니다.
    4. 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로 표시됩니다.

    5. 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로 나옵니다.

    6. UNCACHEABLE SUBQUERY (MySQL 8.0.23 이후부터 등장)

      • 옵티마이저가 내부적으로 캐시(메모이제이션)하지 못하는 서브쿼리입니다.
    7. UNION

      • 여러 SELECT를 UNION으로 합치는 UNION 블록 안의 각각의 SELECT에 붙습니다.
    8. UNION RESULT

      • UNION으로 합친 후 최종 결과를 처리하는 단계에 붙습니다.

3. table 칼럼

  • 해당 단계에서 접근하고 있는 “테이블 또는 자료원”의 이름을 표시합니다.
  • 실제 물리 테이블명뿐만 아니라, “임시 테이블(derived)”, “상수 테이블(const)”, “시스템 테이블(system)” 등도 나올 수 있습니다.

  • 대표적인 값(종류)

    1. 실제 테이블명

      • 예: users, orders, T1, employee 등 사용자가 생성한 물리 테이블 이름.
    2. const

      • 테이블이 정확히 1행만 가진다고 옵티마이저가 판단될 때(예: PRIMARY KEY = 상수 조회)
      • 즉, 해당 쿼리가 “코스트(cost)가 거의 0”이라고 판단되면 const로 표시되며, 메모리 내에서 상수화한 단일 행으로 간주됩니다.
    3. system

      • 테이블의 크기가 1행뿐인, 극도로 작은 테이블(예: MySQL이 내부적으로 관리하는 메타 테이블)일 때 사용됩니다.
    4. derived

      • FROM절에서 서브쿼리를 통해 만들어진 “임시 테이블(derived table)”을 나타냅니다.
      • 예: FROM (SELECT …) AS sub 구문의 derived 결과.
    5. **** (또는 `dep_subq`)

      • WHERE, SELECT 절 등에서 실행되는 “non-derived 서브쿼리”를 메모리 내 일시 테이블로 만들 때 이 이름을 사용합니다.
    6. ****, ****

      • UNION 블록 내부에서 “각각의 SELECT → 합쳐진 결과”를 임시로 처리할 때 사용됩니다.
    7. NULL

      • (드물게) 내부 옵티마이저 단계에서 어떤 물리 테이블도 접근하지 않을 때 NULL로 표시됩니다.

4. partitions 칼럼

  • 파티셔닝(partitioned table)을 사용 중이라면, 이 단계에서 액세스하는 “파티션 이름(PARTITION)” 목록을 보여줍니다.
  • 파티션이 지정되지 않았거나, 파티셔닝 하지 않은 테이블이라면 NULL이 뜹니다.

  • 대표적인 값(종류)

    1. 파티션 이름 리스트(콤마 구분)

      • 예: p0,p1,p2
      • 또는 p2024_01, p2024_02 같은 실제 파티션 이름.
    2. NULL

      • 파티셔닝을 사용하지 않았거나, 옵티마이저가 파티션 병합 혹은 전체 스캔으로 판단해 “특정 파티션만 접근하지 않는 경우”.

5. type 칼럼 (조인 타입/접근 방식)

  • MySQL 옵티마이저가 해당 테이블을 “어떤 방식으로 접근(스캔)하는지”를 나타냅니다.
  • 성능 순서는 아래 예시 순서대로 좋음 → 나쁨(왼쪽이 좋고, 오른쪽이 나쁨):

    system < const < eq_ref < ref < fulltext < ref_or_null < index_merge 
    < unique_subquery < index_subquery < range < index < ALL
    
  • 대표적인 값(종류)

    1. system

      • 해당 테이블(또는 파티션)에 행이 오직 1개뿐이라 내부적으로 ‘system catalog’ 테이블처럼 처리할 때.
    2. const

      • 상수 기반 조회(프라이머리 키 또는 유니크 키 = 상수)로 “최대 1행”을 읽을 때.
    3. eq_ref

      • 조인 시, “한 행당 딱 하나의 매칭 행만 찾아올 수 있는(= =)” 방식.
      • 보통 PK/FK로 조인하거나 유니크 인덱스 1건 조회일 때.
    4. ref

      • “비-유니크 인덱스를 사용해, 일치하는 모든 행을 가져올 가능성”이 있을 때.
      • 예: WHERE non_unique_col = constant 형태.
    5. fulltext

      • Fulltext 인덱스 검색 시 사용됩니다.
    6. ref_or_null

      • WHERE col = 상수 OR col IS NULL 같이 NULL까지 매칭해야 할 때.
    7. index_merge

      • 여러 인덱스를 동시에 사용해 결과를 합치는 방식.
      • 예: (idx_a(a), idx_b(b)) 두 개의 인덱스 스캔 후 교집합(Intersection)해서 결과를 내는 경우.
    8. unique_subquery

      • 인덱스 조건으로 서브쿼리를 하나씩 조인할 때, 내부적으로 비슷한 “인덱스-서브쿼리” 방식이 쓰입니다.
    9. index_subquery

      • unique_subquery와 유사하지만, 유니크 제약이 없는 인덱스를 쓸 때.
    10. range

      • 범위 스캔(범위 조건)으로 “인덱스의 일부 구간(A > 100, B BETWEEN 10 AND 20 등)”을 연속으로 탐색할 때.
    11. index

      • 인덱스 파일(리프 노드) 전체를 순차 스캔하는 방식.
      • 예: 커버링 인덱스(cov‌ering index)를 읽을 때, ORDER BY나 GROUP BY가 인덱스 순서와 맞아떨어질 경우.
    12. ALL

      • 테이블 전체를 풀 스캔(Full Table Scan)하는 방식.
      • 가장 비용이 많이 드는 접근 방식으로, 가능한 한 피해야 합니다.

6. possible_keys 칼럼

  • 옵티마이저가 “이 테이블에 대해 사용할 수 있다고 판단한(후보가 된) 인덱스 목록”을 콤마로 나열합니다.
  • 실제로 그 조건에 맞는 모든 인덱스를 나열하며, 만약 후보가 없으면 NULL로 표시됩니다.

  • 대표적인 값(종류)

    1. 인덱스 이름 목록(콤마 구분)

      • 예: idx_userid,idx_created_at
      • 위 예시라면, WHERE userid = ... AND created_at > ... 형태에서 두 인덱스가 후보로 떠 있음을 뜻합니다.
    2. NULL

      • 이 테이블을 위해 “사용 가능한 인덱스 후보가 전혀 없다”고 판단되는 경우.
      • 예: 조건절에 인덱스 컬럼이 하나도 걸리지 않았거나, 함수 (YEAR(col) = ...)처럼 인덱스가 무력화된 경우.

7. key 칼럼

  • possible_keys 중에서 실제로 “선택되어서 사용된 인덱스” 이름을 보여줍니다.
  • 만약 옵티마이저가 “인덱스를 사용하는 것이 오히려 비효율적”이라고 판단하면 NULL로 나타나며, 이때는 풀 스캔(ALL) 같은 방식이 선택됩니다.

  • 대표적인 값(종류)

    1. 인덱스 이름

      • 예: PRIMARY(프라이머리 키), idx_userid, idx_date
    2. NULL

      • 실제 사용된 인덱스가 없어서, 테이블 풀 스캔(ALL) 또는 다른 방식(예: range→인덱스를 활용 못 함)으로 진행하는 경우.

8. key_len 칼럼

  • 실제로 사용된 인덱스(key)의 바이트(byte) 길이를 나타냅니다.
  • 인덱스에 걸린 컬럼 데이터 타입과 순서에 따라 바이트 수가 달라집니다.
  • 숫자형은 4~8바이트, 문자열(VARCHAR)이나 CHAR는 그 길이에 따라 달라집니다.

  • 대표적인 값(종류)

    1. 정수값(n)

      • 예: 4, 5, 8, 9
      • 예를 들어, INT 단일 컬럼 인덱스만 썼다면 4(4바이트), (col1(INT), col2(INT)) 조합 인덱스를 썼다면 8(4바이트+4바이트) 등이 나옵니다.
      • 문자형 VARCHAR(10)이 UTF-8 3바이트 문자로 인덱싱되면 최대 30(10×3) 등으로 계산됩니다.
    2. NULL

      • 인덱스를 사용하지 않거나, 정보가 표시되지 않는 경우(예: FULLTEXT 스캔 시).

9. ref 칼럼

  • 인덱스가 사용될 때, 그 인덱스 어떤 값에 매칭해서 접근했는지를 보여줍니다.
  • 보통 “컬럼명” 또는 “상수(const)” 형태로 표시되고, 여러 값을 사용하는 경우 const,const처럼 콤마로 구분됩니다.

  • 대표적인 값(종류)

    1. 상수 값 또는 컬럼명

      • 예: const

        • WHERE id = 123 같은 경우, “id 컬럼을 상수 123과 비교”하므로 ref = const로 표시됩니다.
      • 예: T1.col_name

        • 조인 시 “다른 테이블의 컬럼”과 비교하여 인덱스를 타는 경우, ON T1.id = T2.id라면 ref = T2.id처럼 나옵니다.
    2. 복합(콤마 구분)

      • 여러 컬럼이 결합된 인덱스를 쓸 때, 예를 들어 (col1, col2) 인덱스를 WHERE col1 = 1 AND col2 = 2로 사용한다면 ref = const,const 또는 ref = ‘1’,‘2’처럼 표시됩니다.
    3. NULL

      • 인덱스를 사용하지 않거나, ALL(풀 스캔)처럼 인덱스 외 방법을 쓸 때.

10. rows 칼럼

  • 설명

    • 옵티마이저가 추정한(estimated) “이 단계에서 스캔해야 할(읽어야 할) 행(row) 수”입니다.
    • 실제 실행 시 읽은 행 수가 궁금하면 MySQL 8.0.18+버전에서 EXPLAIN ANALYZE를 사용해야 합니다.
  • 대표적인 값(종류)

    1. 정수값(n)

      • 예: 1, 10, 1000, 50000
      • 인덱스를 잘 탄다면 rows1~10 정도로 보여지고, 풀 스캔이면 몇만 건 이상으로 표시될 수 있습니다.
    2. NULL

      • 이 단계에서 읽을 행 수를 추정할 수 없거나, 드문 경우에 표기되지 않을 때(예: 특정 내부 임시 테이블 단계).

11. filtered 칼럼

  • 해당 단계에서 “조건절(WHERE)이 얼마나 걸러내는지(percentage)”를 추정한 값입니다.
  • 값은 0.00~100.00 사이의 소수점 퍼센트이며, 예를 들어 filtered = 50.00이면 “추정 상 이 단계에서 50%의 행만 다음 단계로 넘길 것”이라는 의미입니다.

  • 대표적인 값(종류)

    1. 0.00 ~ 100.00 (소수점)

      • 예: 100.00 (조건절이 없어서 모든 행을 넘긴다고 추정하거나, 인덱스가 커버링되었을 때)
      • 예: 10.00 (조건절에서 10%만 다음 단계로 남는다)
    2. NULL

      • “조건 절이 없어서(filtered 개념 자체가 없는 단계)”인 경우
      • 내부 파티션 단계 등에서 표시되지 않을 때.

12. Extra 칼럼

  • 옵티마이저가 실행 계획에 대해 추가로 알려주는 “특이사항”을 나타냅니다.
  • 복수의 메시지를 띄울 수 있으며, 콤마(또는 스페이스)로 구분됩니다.

  • 대표적인 값(종류)

    1. Using where

      • 이 단계에서 “WHERE 조건을 평가해서 필터링”을 수행함을 의미합니다.
    2. Using index

      • “커버링 인덱스(Covering Index)”만으로 결과를 반환하므로, 추가로 테이블(레코드)을 읽지 않음을 의미합니다.
    3. Using index condition

      • 인덱스 조건 푸시다운(Index Condition Pushdown)을 통해, 인덱스 레벨에서 일부 WHERE 절을 처리할 때 표시됩니다.
    4. Using temporary

      • 결과를 정렬·그룹화하기 위해 “임시 테이블(Temporary Table)”을 메모리/디스크에 생성함을 의미합니다.
    5. Using filesort

      • ORDER BY나 GROUP BY를 수행할 때, “인덱스를 사용한 정렬이 불가능해 임시로 파일 또는 메모리 정렬”(filesort)을 해야 함을 의미합니다.
    6. Using join buffer (Block Nested-Loop)

      • 조인 시 BNL(Block Nested Loop) 방식으로 처리하며, 메모리 상에 join buffer를 만들어 사용함을 뜻합니다.
    7. Impossible WHERE

      • WHERE 조건이 “절대 참(true)이 될 수 없는 식”으로 옵티마이저가 판단했을 때 예외적으로 표시됩니다.
    8. No matching index

      • 조인을 수행할 때 “조인 조건에 사용할 수 있는 인덱스가 전혀 없어서, 풀 스캔(all)만 가능”하다는 의미입니다.
    9. Distinct

      • SELECT DISTINCT …를 수행하는 단계임을 나타냅니다.
    10. Select tables optimized away

      • 아무 테이블도 액세스할 필요 없이(예: SELECT 1+1) 바로 상수 반환만 하면 되는 경우.
    11. Metadata Lock

      • DDL/DDL lock이 있는 상태에서, 메타데이터 락(MD locking) 관련 메시지가 나옵니다.
    12. Using MRR (Multi-Range Read)

      • InnoDB 스토리지 엔진이 “multi-range read” 최적화를 사용했을 때 표시됩니다.
    13. Using union(derived)

      • Derived table(파생 테이블)을 union 형태로 최적화했음을 의미합니다.
    14. Materialized (MySQL 8.0 이후)

      • CTE(Common Table Expressions, WITH문)를 Materialize 했음을 의미합니다.
    15. Ref access on null key

      • 조인 시 NULL을 키로 접근했다는 뜻(예: WHERE col IS NULL 인덱스 범위 접근).
This post is licensed under CC BY 4.0 by the author.