메인 콘텐츠로 건너뛰기
SQL 문의 실행 계획을 보여줍니다.
구문:
EXPLAIN [AST | SYNTAX | QUERY TREE | PLAN | PIPELINE | ESTIMATE | TABLE OVERRIDE] [setting = value, ...]
    [
      SELECT ... |
      tableFunction(...) [COLUMNS (...)] [ORDER BY ...] [PARTITION BY ...] [PRIMARY KEY] [SAMPLE BY ...] [TTL ...]
    ]
    [FORMAT ...]
예시:
EXPLAIN SELECT sum(number) FROM numbers(10) UNION ALL SELECT sum(number) FROM numbers(10) ORDER BY sum(number) ASC FORMAT TSV;
Union
  Expression (Projection)
    Expression (Before ORDER BY and SELECT)
      Aggregating
        Expression (Before GROUP BY)
          SettingQuotaAndLimits (Set limits and quota after reading from storage)
            ReadFromStorage (SystemNumbers)
  Expression (Projection)
    MergingSorted (Merge sorted streams for ORDER BY)
      MergeSorting (Merge sorted blocks for ORDER BY)
        PartialSorting (Sort each block for ORDER BY)
          Expression (Before ORDER BY and SELECT)
            Aggregating
              Expression (Before GROUP BY)
                SettingQuotaAndLimits (Set limits and quota after reading from storage)
                  ReadFromStorage (SystemNumbers)

EXPLAIN 타입

  • AST — 추상 구문 트리입니다.
  • SYNTAX — AST 수준 최적화가 적용된 후의 쿼리 텍스트입니다.
  • QUERY TREE — 쿼리 트리 수준 최적화가 적용된 후의 쿼리 트리입니다.
  • PLAN — 쿼리 실행 계획입니다.
  • PIPELINE — 쿼리 실행 파이프라인입니다.

EXPLAIN AST

쿼리 AST를 출력합니다. SELECT뿐 아니라 모든 쿼리를 지원합니다. 설정:
  • graphDOT 그래프 기술 언어로 표현된 그래프 형태로 AST를 출력합니다. 기본값: 0.
예시:
EXPLAIN AST SELECT 1;
SelectWithUnionQuery (children 1)
 ExpressionList (children 1)
  SelectQuery (children 1)
   ExpressionList (children 1)
    Literal UInt64_1
EXPLAIN AST ALTER TABLE t1 DELETE WHERE date = today();
  explain
  AlterQuery  t1 (children 1)
   ExpressionList (children 1)
    AlterCommand 27 (children 1)
     Function equals (children 1)
      ExpressionList (children 2)
       Identifier date
       Function today (children 1)
        ExpressionList

EXPLAIN 구문

구문 분석이 끝난 후 쿼리의 추상 구문 트리(AST)를 표시합니다. 쿼리를 파싱해 쿼리 AST와 쿼리 트리를 구성하고, 필요에 따라 쿼리 분석기와 최적화 패스를 실행한 뒤, 쿼리 트리를 다시 쿼리 AST로 변환하는 방식으로 수행됩니다. 설정:
  • oneline – 쿼리를 한 줄로 출력합니다. 기본값: 0.
  • run_query_tree_passes – 쿼리 트리를 덤프하기 전에 쿼리 트리 패스를 실행합니다. 기본값: 0.
  • query_tree_passesrun_query_tree_passes가 설정된 경우 실행할 패스 수를 지정합니다. query_tree_passes를 지정하지 않으면 모든 패스를 실행합니다.
예시:
Query
EXPLAIN SYNTAX SELECT * FROM system.numbers AS a, system.numbers AS b, system.numbers AS c WHERE a.number = b.number AND b.number = c.number;
Response
SELECT *
FROM system.numbers AS a, system.numbers AS b, system.numbers AS c
WHERE (a.number = b.number) AND (b.number = c.number)
run_query_tree_passes를 사용하는 경우:
Query
EXPLAIN SYNTAX run_query_tree_passes = 1 SELECT * FROM system.numbers AS a, system.numbers AS b, system.numbers AS c WHERE a.number = b.number AND b.number = c.number;
Response
SELECT
    __table1.number AS `a.number`,
    __table2.number AS `b.number`,
    __table3.number AS `c.number`
FROM system.numbers AS __table1
ALL INNER JOIN system.numbers AS __table2 ON __table1.number = __table2.number
ALL INNER JOIN system.numbers AS __table3 ON __table2.number = __table3.number

EXPLAIN QUERY TREE

설정:
  • run_passes — 쿼리 트리를 덤프하기 전에 모든 쿼리 트리 패스를 실행합니다. 기본값: 1.
  • dump_passes — 쿼리 트리를 덤프하기 전에 사용된 패스 정보를 덤프합니다. 기본값: 0.
  • passes — 실행할 패스 수를 지정합니다. -1로 설정하면 모든 패스를 실행합니다. 기본값: -1.
  • dump_tree — 쿼리 트리를 표시합니다. 기본값: 1.
  • dump_ast — 쿼리 트리에서 생성된 쿼리 AST를 표시합니다. 기본값: 0.
예시:
EXPLAIN QUERY TREE SELECT id, value FROM test_table;
QUERY id: 0
  PROJECTION COLUMNS
    id UInt64
    value String
  PROJECTION
    LIST id: 1, nodes: 2
      COLUMN id: 2, column_name: id, result_type: UInt64, source_id: 3
      COLUMN id: 4, column_name: value, result_type: String, source_id: 3
  JOIN TREE
    TABLE id: 3, table_name: default.test_table

EXPLAIN PLAN

쿼리 계획 단계를 덤프합니다. 설정:
  • optimize — 계획을 표시하기 전에 쿼리 계획 최적화를 적용할지 제어합니다. 기본값: 1.
  • header — 단계의 출력 헤더를 출력합니다. 기본값: 0.
  • description — 단계 설명을 출력합니다. 기본값: 1.
  • indexes — 사용된 인덱스와, 적용된 각 인덱스별로 필터링된 파트 수 및 필터링된 그래뉼 수를 표시합니다. 기본값: 0. MergeTree 테이블에서 지원됩니다. ClickHouse >= v25.9부터는 이 문을 SETTINGS use_query_condition_cache = 0, use_skip_indexes_on_data_read = 0와 함께 사용할 때만 출력이 적절하게 표시됩니다.
  • projections — 분석된 모든 프로젝션과, 프로젝션 프라이머리 키 조건에 기반한 파트 수준 필터링에 미치는 영향을 표시합니다. 각 프로젝션에 대해 이 섹션에는 프로젝션의 프라이머리 키를 사용해 평가된 파트 수, 행 수, 마크 수, 범위 수 등의 통계가 포함됩니다. 또한 프로젝션 자체를 읽지 않고도 이 필터링으로 인해 건너뛴 데이터 파트 수를 보여줍니다. 프로젝션이 실제 읽기에 사용되었는지, 아니면 필터링용으로만 분석되었는지는 description 필드로 확인할 수 있습니다. 기본값: 0. MergeTree 테이블에서 지원됩니다.
  • actions — 단계 작업에 대한 자세한 정보를 출력합니다. 기본값: 0.
  • sorting — 정렬된 출력을 생성하는 각 계획 단계의 정렬 설명을 출력합니다. 기본값: 0.
  • keep_logical_steps — 조인을 물리적 조인 구현으로 변환하지 않고 논리적 계획 단계를 유지합니다. 기본값: 0.
  • json — 쿼리 계획 단계를 JSON 포맷의 한 행으로 출력합니다. 기본값: 0. 불필요한 이스케이프를 피하려면 TabSeparatedRaw (TSVRaw) 포맷을 사용하는 것이 좋습니다.
  • input_headers — 단계의 입력 헤더를 출력합니다. 기본값: 0. 주로 입력-출력 헤더 불일치와 관련된 문제를 디버깅하는 개발자에게만 유용합니다.
  • column_structure — 헤더에서 컬럼의 이름과 유형뿐 아니라 구조도 함께 출력합니다. 기본값: 0. 주로 입력-출력 헤더 불일치와 관련된 문제를 디버깅하는 개발자에게만 유용합니다.
  • distributed — 분산 테이블 또는 병렬 레플리카의 원격 노드에서 실행되는 쿼리 계획을 표시합니다. 기본값: 0.
  • compact — 활성화하면 계획에서 표현식 단계와 자세한 작업 정보(입력, 함수, Aliases, 출력 위치)를 숨깁니다. actions = 1일 때만 효과가 있습니다. 기본값: 0.
  • pretty — 들여쓰기 대신 선 그리기 문자(├──, └──, │)를 사용해 계층 구조를 시각화한 계획 트리를 출력합니다. 또한 조인 단계 속성을 인라인으로 포맷합니다. 기본값: 0.
json=1이면 단계 이름에 고유한 단계 식별자를 나타내는 추가 접미사가 포함됩니다. 예시:
EXPLAIN SELECT sum(number) FROM numbers(10) GROUP BY number % 4;
Union
  Expression (Projection)
  Expression (Before ORDER BY and SELECT)
    Aggregating
      Expression (Before GROUP BY)
        SettingQuotaAndLimits (Set limits and quota after reading from storage)
          ReadFromStorage (SystemNumbers)
단계별 비용 및 쿼리 비용 추정은 지원되지 않습니다.
json = 1인 경우 쿼리 계획은 JSON 포맷으로 표시됩니다. 각 노드는 항상 Node TypePlans 키를 갖는 딕셔너리입니다. Node Type은 단계 이름을 나타내는 문자열입니다. Plans는 하위 단계 설명을 담은 배열입니다. 그 밖의 선택적 키는 노드 유형과 설정에 따라 추가될 수 있습니다. 예시:
EXPLAIN json = 1, description = 0 SELECT 1 UNION ALL SELECT 2 FORMAT TSVRaw;
[
  {
    "Plan": {
      "Node Type": "Union",
      "Node Id": "Union_10",
      "Plans": [
        {
          "Node Type": "Expression",
          "Node Id": "Expression_13",
          "Plans": [
            {
              "Node Type": "ReadFromStorage",
              "Node Id": "ReadFromStorage_0"
            }
          ]
        },
        {
          "Node Type": "Expression",
          "Node Id": "Expression_16",
          "Plans": [
            {
              "Node Type": "ReadFromStorage",
              "Node Id": "ReadFromStorage_4"
            }
          ]
        }
      ]
    }
  }
]
description = 1이면 해당 단계에 Description 키가 추가됩니다:
{
  "Node Type": "ReadFromStorage",
  "Description": "SystemOne"
}
header = 1이면 Header 키가 컬럼 배열 형태로 해당 단계에 추가됩니다. 예시:
EXPLAIN json = 1, description = 0, header = 1 SELECT 1, 2 + dummy;
[
  {
    "Plan": {
      "Node Type": "Expression",
      "Node Id": "Expression_5",
      "Header": [
        {
          "Name": "1",
          "Type": "UInt8"
        },
        {
          "Name": "plus(2, dummy)",
          "Type": "UInt16"
        }
      ],
      "Plans": [
        {
          "Node Type": "ReadFromStorage",
          "Node Id": "ReadFromStorage_0",
          "Header": [
            {
              "Name": "dummy",
              "Type": "UInt8"
            }
          ]
        }
      ]
    }
  }
]
indexes = 1이면 Indexes 키가 추가됩니다. 이 키에는 사용된 인덱스의 배열이 포함됩니다. 각 인덱스는 Type 키(문자열 Partition Min-Max, Partition, Statistics, PrimaryKey 또는 Skip)와 선택적 키를 포함하는 JSON으로 설명됩니다.
  • Name — 인덱스 이름입니다(현재는 Skip 인덱스에만 사용됩니다).
  • Keys — 인덱스에 사용되는 컬럼 배열입니다.
  • Condition — 사용된 조건입니다.
  • Description — 인덱스 설명입니다(현재는 Skip 인덱스에만 사용됩니다).
  • Parts — 인덱스 적용 전/후의 파트 수입니다.
  • Granules — 인덱스 적용 전/후의 그래뉼 수입니다.
  • Ranges — 인덱스 적용 후의 그래뉼 범위 수입니다.
예시:
"Node Type": "ReadFromMergeTree",
"Indexes": [
  {
    "Type": "Partition Min-Max",
    "Keys": ["y"],
    "Condition": "(y in [1, +inf))",
    "Parts": 4/5,
    "Granules": 11/12
  },
  {
    "Type": "Partition",
    "Keys": ["y", "bitAnd(z, 3)"],
    "Condition": "and((bitAnd(z, 3) not in [1, 1]), and((y in [1, +inf)), (bitAnd(z, 3) not in [1, 1])))",
    "Parts": 3/4,
    "Granules": 10/11
  },
  {
    "Type": "PrimaryKey",
    "Keys": ["x", "y"],
    "Condition": "and((x in [11, +inf)), (y in [1, +inf)))",
    "Parts": 2/3,
    "Granules": 6/10,
    "Search Algorithm": "generic exclusion search"
  },
  {
    "Type": "Skip",
    "Name": "t_minmax",
    "Description": "minmax GRANULARITY 2",
    "Parts": 1/2,
    "Granules": 2/6
  },
  {
    "Type": "Skip",
    "Name": "t_set",
    "Description": "set GRANULARITY 2",
    "": 1/1,
    "Granules": 1/2
  }
]
projections = 1이면 Projections 키가 추가됩니다. 여기에는 분석된 프로젝션의 배열이 포함됩니다. 각 프로젝션은 다음 키를 포함하는 JSON으로 표시됩니다.
  • Name — 프로젝션 이름입니다.
  • Condition — 사용된 프로젝션 프라이머리 키(primary key) 조건입니다.
  • Description — 프로젝션이 사용되는 방식에 대한 설명입니다(예: 파트 수준 필터링).
  • Selected Parts — 프로젝션으로 선택된 파트 수입니다.
  • Selected Marks — 선택된 마크 수입니다.
  • Selected Ranges — 선택된 범위 수입니다.
  • Selected Rows — 선택된 행 수입니다.
  • Filtered Parts — 파트 수준 필터링으로 건너뛴 파트 수입니다.
예시:
"Node Type": "ReadFromMergeTree",
"Projections": [
  {
    "Name": "region_proj",
    "Description": "Projection has been analyzed and is used for part-level filtering",
    "Condition": "(region in ['us_west', 'us_west'])",
    "Search Algorithm": "binary search",
    "Selected Parts": 3,
    "Selected Marks": 3,
    "Selected Ranges": 3,
    "Selected Rows": 3,
    "Filtered Parts": 2
  },
  {
    "Name": "user_id_proj",
    "Description": "Projection has been analyzed and is used for part-level filtering",
    "Condition": "(user_id in [107, 107])",
    "Search Algorithm": "binary search",
    "Selected Parts": 1,
    "Selected Marks": 1,
    "Selected Ranges": 1,
    "Selected Rows": 1,
    "Filtered Parts": 2
  }
]
actions = 1로 설정하면, 추가되는 키는 단계 유형에 따라 달라집니다. 예시:
EXPLAIN json = 1, actions = 1, description = 0 SELECT 1 FORMAT TSVRaw;
[
  {
    "Plan": {
      "Node Type": "Expression",
      "Node Id": "Expression_5",
      "Expression": {
        "Inputs": [
          {
            "Name": "dummy",
            "Type": "UInt8"
          }
        ],
        "Actions": [
          {
            "Node Type": "INPUT",
            "Result Type": "UInt8",
            "Result Name": "dummy",
            "Arguments": [0],
            "Removed Arguments": [0],
            "Result": 0
          },
          {
            "Node Type": "COLUMN",
            "Result Type": "UInt8",
            "Result Name": "1",
            "Column": "Const(UInt8)",
            "Arguments": [],
            "Removed Arguments": [],
            "Result": 1
          }
        ],
        "Outputs": [
          {
            "Name": "1",
            "Type": "UInt8"
          }
        ],
        "Positions": [1]
      },
      "Plans": [
        {
          "Node Type": "ReadFromStorage",
          "Node Id": "ReadFromStorage_0"
        }
      ]
    }
  }
]
compact = 1을 설정하면 각 Expression 단계가 제거됩니다. 또한 actions = 1이 설정된 경우, ActionsPositions 항목이 숨겨지고 단계 설명만 표시됩니다:
EXPLAIN actions = 1, compact = 1 SELECT sum(number) FROM numbers(10) GROUP BY number % 4 FORMAT Raw;
Aggregating
Keys: modulo(__table1.number, 4_UInt8)
Aggregates:
    sum(__table1.number)
      Function: sum(UInt64) → UInt64
      Arguments: __table1.number
Skip merging: 0
  ReadFromSystemNumbers
distributed = 1로 설정하면 로컬 쿼리 플랜뿐만 아니라 원격 노드에서 실행될 쿼리 플랜도 출력에 포함됩니다. 분산 쿼리를 분석하고 디버깅할 때 유용합니다. 분산 테이블(Distributed Table)을 사용한 예시:
EXPLAIN distributed=1 SELECT * FROM remote('127.0.0.{1,2}', numbers(2)) WHERE number = 1;
Union
  Expression ((Project names + (Projection + (Change column names to column identifiers + (Project names + Projection)))))
    Filter ((WHERE + Change column names to column identifiers))
      ReadFromSystemNumbers
  Expression ((Project names + (Projection + Change column names to column identifiers)))
    ReadFromRemote (Read from remote replica)
      Expression ((Project names + Projection))
        Filter ((WHERE + Change column names to column identifiers))
          ReadFromSystemNumbers
병렬 레플리카를 사용한 예시:
SET enable_parallel_replicas = 2, max_parallel_replicas = 2, cluster_for_parallel_replicas = 'default';

EXPLAIN distributed=1 SELECT sum(number) FROM test_table GROUP BY number % 4;
Expression ((Project names + Projection))
  MergingAggregated
    Union
      Aggregating
        Expression ((Before GROUP BY + Change column names to column identifiers))
          ReadFromMergeTree (default.test_table)
      ReadFromRemoteParallelReplicas
        BlocksMarshalling
          Aggregating
            Expression ((Before GROUP BY + Change column names to column identifiers))
              ReadFromMergeTree (default.test_table)
두 예시 모두에서 쿼리 플랜은 로컬 및 원격 단계를 포함한 전체 실행 흐름을 표시합니다. pretty = 1로 설정하면 들여쓰기 대신 선 그리기 문자를 사용하여 플랜 트리가 표시되며, 주요 단계에 대한 추가 정보도 함께 표시됩니다.
  • 쿼리 출력 컬럼은 계획 맨 위에 표시됩니다.
  • 필터, 집계 키, 정렬 설명, 윈도우 함수의 표현식은 사람이 읽기 쉬운 SQL 유사 표기법으로 표시됩니다(예: greater(plus(a, 1), 5) 대신 a + 1 > 5). 가독성을 위해 내부 컬럼 식별자 프리픽스(예: __table1.)는 제거됩니다.
  • 소스 단계(예: ReadFromMergeTree)는 출력 컬럼을 표시합니다.
  • 필터 단계는 SQL 표기법으로 필터 조건을 표시합니다. 런타임 조인 필터가 있으면 별도로 표시됩니다.
  • 집계 단계는 키와 집계 함수를 해당 인수와 함께 표시합니다(예: sum(c), count()).
  • 튜플 리터럴의 IN 집합은 값이 표시되며(큰 집합은 잘려서 표시됨), 서브쿼리 기반 집합에는 subquery1, subquery2 등의 레이블이 붙고, Set 엔진 테이블의 집합은 테이블 이름을 표시합니다.
  • 조인 단계는 수학 표기법으로 조인 릴레이션, 예상 결과 행 수, 그리고 어떤 출력 컬럼이 왼쪽과 오른쪽에서 오는지를 표시합니다. 다음 기호는 서로 다른 조인 유형을 나타내는 데 사용됩니다:
기호조인 유형
내부 조인
왼쪽 조인
오른쪽 조인
전체 조인
왼쪽 세미 조인
오른쪽 세미 조인
with strikethrough왼쪽 안티 조인
with strikethrough오른쪽 안티 조인
×크로스 조인
예를 들어, t1 ⟕ t2는 테이블 t1t2 사이의 왼쪽 조인을 의미합니다. 테이블 이름 뒤 대괄호 안의 숫자(예: t1[100])는 예상 행 수를 나타내며, 테이블 통계를 사용할 수 있을 때 표시됩니다. pretty 옵션은 compact = 1과 함께 사용하면 효과적이며, 이 경우 Expression 단계와 자세한 작업 정보가 숨겨져 계획을 더 읽기 쉽게 만듭니다.
EXPLAIN pretty = 1 SELECT sum(number) FROM numbers(10) GROUP BY number % 4 FORMAT Raw;
Expression ((Project names + Projection))
└──Aggregating
   └──Expression ((Before GROUP BY + Change column names to column identifiers))
      └──ReadFromSystemNumbers
조인을 사용하는 더 자세한 예시:
CREATE TABLE t1 (id UInt64, value String) ENGINE = MergeTree ORDER BY id;
CREATE TABLE t2 (id UInt64, value String) ENGINE = MergeTree ORDER BY id;
INSERT INTO t1 SELECT number, toString(number) FROM numbers(100);
INSERT INTO t2 SELECT number, toString(number) FROM numbers(100);

EXPLAIN actions = 1, compact = 1, pretty = 1
SELECT * FROM t1 INNER JOIN t2 ON t1.id = t2.id FORMAT Raw;
Output: id, value, t2.id, t2.value

Join (JOIN FillRightFirst)
│  t1[100] ⋈ t2[100]
│  Type: inner | Strictness: all | Algorithm: ConcurrentHashJoin
│  Result rows: 100
│  Output:
│    Left:  id, value
│    Right: id, value
│  Join conditions: id = id
├──ReadFromMergeTree (default.t1)
│     Read type: Default
│     Parts: 1 | Granules: 1
│     Output: id, value
└──ReadFromMergeTree (default.t2)
      Read type: Default
      Parts: 1 | Granules: 1
      Output: id, value

EXPLAIN PIPELINE

설정:
  • header — 각 출력 포트의 헤더를 출력합니다. 기본값: 0입니다.
  • graphDOT 그래프 설명 언어로 작성된 그래프를 출력합니다. 기본값: 0입니다.
  • compactgraph 설정이 활성화되면 그래프를 compact 모드로 출력합니다. 기본값: 1입니다.
compact=0이고 graph=1이면 프로세서 이름에 고유한 프로세서 식별자를 나타내는 추가 접미사가 포함됩니다. 예시:
EXPLAIN PIPELINE SELECT sum(number) FROM numbers_mt(100000) GROUP BY number % 4;
(Union)
(Expression)
ExpressionTransform
  (Expression)
  ExpressionTransform
    (Aggregating)
    Resize 21
      AggregatingTransform × 2
        (Expression)
        ExpressionTransform × 2
          (SettingQuotaAndLimits)
            (ReadFromStorage)
            NumbersRange × 2 01

EXPLAIN ESTIMATE

쿼리를 처리하는 동안 테이블에서 읽게 될 것으로 예상되는 행 수, 마크 수, 파트 수를 표시합니다. MergeTree 계열 테이블에서 동작합니다. 예시 테이블 생성:
Query
CREATE TABLE ttt (i Int64) ENGINE = MergeTree() ORDER BY i SETTINGS index_granularity = 16, write_final_mark = 0;
INSERT INTO ttt SELECT number FROM numbers(128);
OPTIMIZE TABLE ttt;
Query
EXPLAIN ESTIMATE SELECT * FROM ttt;
Response
┌─database─┬─table─┬─parts─┬─rows─┬─marks─┐
│ default  │ ttt   │     1 │  128 │     8 │
└──────────┴───────┴───────┴──────┴───────┘

EXPLAIN TABLE OVERRIDE

테이블 함수를 통해 접근한 테이블 스키마(schema)에 테이블 재정의를 적용한 결과를 보여줍니다. 또한 일부 유효성 검사도 수행하며, 이 재정의로 인해 어떤 형태로든 실패가 발생할 경우 예외를 발생시킵니다. 예시 원격 MySQL 테이블이 다음과 같다고 가정합니다:
Query
CREATE TABLE db.tbl (
    id INT PRIMARY KEY,
    created DATETIME DEFAULT now()
)
Query
EXPLAIN TABLE OVERRIDE mysql('127.0.0.1:3306', 'db', 'tbl', 'root', 'clickhouse')
PARTITION BY toYYYYMM(assumeNotNull(created))
Response
┌─explain─────────────────────────────────────────────────┐
│ PARTITION BY uses columns: `created` Nullable(DateTime) │
└─────────────────────────────────────────────────────────┘
검증이 완전하지 않으므로, 쿼리가 성공하더라도 재정의로 인해 문제가 발생하지 않는다고 보장할 수는 없습니다.
마지막 수정일 2026년 6월 10일