Skip to content
ClickHouse Docs
ClickHouse DocsClickHouse Docs

EXPLAIN

ステートメントの実行計画を表示します。

構文:

EXPLAIN [AST | SYNTAX | QUERY TREE | PLAN | PIPELINE | ANALYZE | ESTIMATE | TABLE OVERRIDE | WHATIF] [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;
Output: sum(number)

Union

EXPLAIN の種類

  • AST — 抽象構文木。
  • SYNTAX — AST レベルでの最適化後のクエリテキスト。
  • QUERY TREE — クエリツリー レベルでの最適化後のクエリツリー。
  • PLAN — クエリ実行プラン。
  • PIPELINE — クエリ実行パイプライン。
  • ANALYZE — クエリを実行し、計測されたランタイムメトリクスを実行計画に注釈として付加します。
  • ESTIMATE — クエリの処理中にテーブルから読み取ると見積もられる行数、マーク数、パーツ数。
  • TABLE OVERRIDE — テーブル関数のスキーマに対するテーブルオーバーライドの検証済み結果。

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 SYNTAX

構文解析後のクエリの抽象構文木 (AST) を表示します。

これは、クエリをパースしてクエリASTとクエリツリーを構築し、必要に応じてクエリアナライザと最適化パスを実行したうえで、クエリツリーをクエリASTに再変換することで行われます。

設定:

  • oneline – クエリを1行で表示します。デフォルト: 0
  • run_query_tree_passes – クエリツリーをダンプする前にクエリツリーパスを実行します。デフォルト: 0
  • query_tree_passesrun_query_tree_passes が設定されている場合、実行するパス数を指定します。query_tree_passes を指定しない場合は、すべてのパスが実行されます。
  • single_record – 整形されたクエリを、行ごとに1レコードではなく単一の複数行レコードとして返します。デフォルト: 1 (explain_syntax_single_record 設定で制御) 。従来の1行1レコード出力に戻すには、0 を設定するか、explain_syntax_single_record = 0 を設定します (グローバルまたはクエリごとの SETTINGS 内) 。または、compatibility26.8 より前の任意のバージョンに設定します。

例:

Querysql
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;
Responsesql
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 を指定した場合:

Querysql
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;
Responsesql
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 — 解析されたすべてのプロジェクションと、プロジェクションの主キー条件に基づくパーツレベルのフィルタリングへの影響を表示します。各プロジェクションについて、このセクションには、プロジェクションの主キーを使って評価されたパーツ数、行数、マーク数、範囲数などの統計が含まれます。また、このフィルタリングにより、プロジェクション自体を読み取ることなくスキップされた data parts の数も表示します。プロジェクションが実際に読み取りに使用されたのか、それともフィルタリングのために解析されただけなのかは、description フィールドで判別できます。デフォルト: 0。MergeTree テーブルでサポートされています。
  • actions — ステップの actions に関する詳細情報を表示します。デフォルト: 1。
  • sorting — ソート済みの出力を生成する各プランステップについて、ソートの説明を表示します。デフォルト: 0。
  • keep_logical_steps — joins について、物理的な join 実装に変換せずに、論理プランステップを保持します。デフォルト: 0。
  • json — クエリプランのステップを JSON フォーマットの 1 行として出力します。デフォルト: 0。不要なエスケープを避けるため、TabSeparatedRaw (TSVRaw) フォーマットの使用を推奨します。
  • input_headers — ステップの入力ヘッダーを表示します。デフォルト: 0。主に、入力ヘッダーと出力ヘッダーの不一致に関する問題をデバッグする開発者にのみ有用です。
  • column_structure — ヘッダー内のカラム構造を、名前と型に加えて表示します。デフォルト: 0。主に、入力ヘッダーと出力ヘッダーの不一致に関する問題をデバッグする開発者にのみ有用です。
  • distributed — 分散テーブルまたは並列レプリカについて、リモートノードで実行されるクエリプランを表示します。json と同時にはサポートされません。デフォルト: 0。
  • compact — 有効にすると、プランから expression ステップと詳細な action 情報 (入力、関数、別名、出力位置) を非表示にします。actions = 1 の場合にのみ効果があります。デフォルト: 1。
  • pretty — インデントの代わりに罫線文字 (├──、└──、│) を使ってプランツリーを表示し、階層構造を視覚化します。さらに、join ステップのプロパティもインラインで整形して表示します。デフォルト: 1。

例:

EXPLAIN SELECT sum(number) FROM numbers(10) GROUP BY number % 4  LIMIT 1;
Output: sum(number)

Limit (preliminary LIMIT)

json = 1 の場合、クエリプランは JSON フォーマットで表されます。各ノードは辞書で、常に Node TypeNode IdPlans のキーを持ちます。Node Type はステップ名を表す文字列で、Node Id は一意のステップ識別子です (数値の接尾辞が付いたステップ名。例: Union_10) 。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 キーが追加されます。これには、使用された索引の配列が含まれます。各索引は JSON で記述され、Type キー (文字列 Partition Min-MaxPartitionStatisticsPrimaryKey または Skip) と、必要に応じて以下のキーを持ちます。

  • 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 — プロジェクションで使用された主キー条件。
  • 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 = 0 かつ actions = 1 を指定すると、Expression ステップとともに式に関する詳細情報を確認できます:

EXPLAIN actions = 1, compact = 0 SELECT sum(number) FROM numbers(10) GROUP BY number % 4;
Output: sum(number)

Expression ((Project names + Projection))
│  Actions: INPUT : 0 -> sum(__table1.number) UInt64 : 0
│           INPUT :: 1 -> modulo(__table1.number, 4_UInt8) UInt8 : 1
│           ALIAS sum(__table1.number) :: 0 -> sum(number) UInt64 : 2
│  Positions: 2
└──Aggregating
   │  Keys: number MOD 4
   │  Aggregates: sum(number)
   │  Skip merging: 0
   └──Expression ((Before GROUP BY + Change column names to column identifiers))
      │  Actions: INPUT : 0 -> number UInt64 : 0
      │           COLUMN Const(UInt8) -> 4_UInt8 UInt8 : 1
      │           ALIAS number :: 0 -> __table1.number UInt64 : 2
      │           FUNCTION modulo(__table1.number : 2, 4_UInt8 :: 1) -> modulo(__table1.number, 4_UInt8) UInt8 : 0
      │  Positions: 0 2
      └──ReadFromSystemNumbers
            Output: number

distributed = 1 を指定すると、出力にはローカルのクエリプランだけでなく、リモートノードで実行されるクエリプランも含まれます。これは、分散クエリの分析やデバッグに役立ちます。

分散テーブルを使用した例:

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 表記のフィルタ条件が表示されます。ランタイム join フィルタが存在する場合は、それらは別個に表示されます。
  • 集約ステップ には、キーと、引数付きの集約関数 (例: sum(c)count()) が表示されます。
  • タプルリテラルの IN set にはその値が表示され (大きな set の場合は切り詰められます) 、サブクエリベースの set には subquery1subquery2 などのラベルが付き、Set engine tables 由来の set にはテーブル名が表示されます。
  • join ステップ には、数学的記法を用いた join 関係、推定結果行数、 およびどの出力カラムが左側と右側のどちらに由来するかが表示されます。異なる join タイプを 表すために、次の記号が使用されます:
Symbol Join Type
Inner Join
Left Join
Right Join
Full Join
Left Semi Join
Right Semi Join
with strikethrough Left Anti Join
with strikethrough Right Anti Join
× Cross Join

たとえば、t1 ⟕ t2 はテーブル t1t2 の left join を意味します。 テーブル名の後の角括弧内の数値 (例: t1[100]) は、テーブル統計が利用可能な場合の 推定行数を示します。

pretty オプションは compact = 1 と組み合わせると効果的で、Expression ステップと詳細な action 情報が非表示になるため、プランが読みやすくなります。

JOINを使った詳しい例:

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, id, value

Join (JOIN FillRightFirst)
│  t1[100] ⋈ t2[100]
│  Type: inner | Strictness: all | Algorithm: SpillingHashJoin(HashJoin)
│  Result rows: 100
│  Join conditions: id = id
│  Output:
│    Left:  id, value
│    Right: id, value
├──ReadFromMergeTree (default.t1)
│     Read type: Default
│     Parts: 1 | Granules: 1
│     Output: id, value
│     Runtime filters: RF1(id, id from default.t2)
└──BuildRuntimeFilter (Build runtime join filter on id)
   │  Filter id: RF1
   │  Source table: default.t2
   └──ReadFromMergeTree (default.t2)
         Read type: Default
         Parts: 1 | Granules: 1
         Output: id, value

EXPLAIN PIPELINE

設定:

  • header — 各出力ポートのヘッダーを表示します。デフォルト: 0。
  • graphDOT グラフ記述言語で記述されたグラフを表示します。デフォルト: 0。
  • compactgraph 設定が有効な場合、compact モードでグラフを表示します。デフォルト: 1。
  • compact_repeated_processor_chains — テキスト出力で、隣接して繰り返されるプロセッサチェーンを、チェーンを 1 つだけ表示して繰り返し回数を付けることでコンパクトにします。これにより、たとえば JOIN で同じチェーンが何度も現れる場合に、並列パイプラインが読みやすくなります。グラフ出力には影響しません。デフォルト: 0。
Resize 16 → 1
  FillingRightJoinSide          │
    SimpleSquashingTransform    │ × 16
      Resize 1 → 16

compact=0 かつ graph=1 の場合、プロセッサ名には一意のプロセッサ識別子を示す追加の接尾辞が含まれます。

例:

EXPLAIN PIPELINE SELECT sum(number) FROM numbers_mt(100000) GROUP BY number % 4;
(Union)
(Expression)
ExpressionTransform
  (Expression)
  ExpressionTransform
    (Aggregating)
    Resize 2

EXPLAIN ANALYZE

EXPLAIN ANALYZE は実際にクエリを実行し、結果の行を破棄したうえで、各ステップに実行時に実際に何が起きたかを注記として付けた、EXPLAIN PLAN と同じプランツリーを出力します。

設定:

EXPLAIN ANALYZE では、EXPLAIN PLAN と同じ表示オプションを使用できます (EXPLAIN PLAN セクションを参照) 。

  • headerEXPLAIN PLAN セクションを参照してください。
  • descriptionEXPLAIN PLAN セクションを参照してください。
  • projectionsEXPLAIN PLAN セクションを参照してください。
  • sortingEXPLAIN PLAN セクションを参照してください。
  • input_headersEXPLAIN PLAN セクションを参照してください。
  • column_structureEXPLAIN PLAN セクションを参照してください。
  • actionsEXPLAIN PLAN セクションを参照してください。デフォルト: 1。
  • indexesEXPLAIN PLAN セクションを参照してください。デフォルト: 1。
  • compactEXPLAIN PLAN セクションを参照してください。デフォルト: 1。
  • prettyEXPLAIN PLAN セクションを参照してください。デフォルト: 1。
  • processorsEXPLAIN ANALYZE では、各ステージについて、プロセッサごとの経過時間分布 (minmedianmaxsum) を示す追加の行を出力します。並列プロセッサ間の負荷の偏りを見つけるのに役立ちます。デフォルト: 0。
  • matchesEXPLAIN ANALYZE では、結合による出力だけではこれらの数値を導出できない場合に、matchedmatch ratefanout メトリクスに必要な追加の記録処理を join ステップで行います。導出できる場合は、このオプションなしで報告されます。join ステップを参照してください。デフォルト: 0。

例:

EXPLAIN ANALYZE SELECT number % 10 AS k, count() FROM numbers_mt(1000000) GROUP BY k;
Query summary:
  Time:        10.72 ms (planning 6.45 ms · execution 4.26 ms)
  Read:        1.00 million rows, 8.00 MB (234.49 million rows/s., 1.88 GB/s.)
  Peak memory: 28.98 KiB

Output: number MOD 10, count()

Expression ((Project names + Projection))
│  I/O: rows 10 → 10 · 90 B → 90 B
│    time 21.82 us (0.5%) · parallelism 0.98/1
└──Aggregating
   │  Keys: number MOD 10
   │  Aggregates: count()
   │  Skip merging: 0
   │  I/O: rows 1.00 million → 10 (0.00%) · 1.00 MB → 90 B
   │    Stage (partial aggregation): time 868.45 us (20.4%) · parallelism 3.80/15
   │    Stage (final aggregation): time 445.27 us (10.4%) · parallelism 1.11/16
   └──Expression ((Before GROUP BY + Change column names to column identifiers))
      │  I/O: rows 1.00 million → 1.00 million · 8.00 MB → 1.00 MB
      │    time 677.07 us (15.9%) · parallelism 4.31/15
      └──ReadFromSystemNumbers
            Output: number
            I/O: rows 0 → 1.00 million · 0 B → 8.00 MB
              time 993.94 us (23.3%) · parallelism 7.52/15

出力を見てみましょう。まずはヘッダーを見てみましょう。

   Query summary:
     Time:        <total> (planning <planning> · execution <execution>)
     Read:        <rows> rows, <bytes> (<rows/s>, <bytes/s>)
     Peak memory: <peak>
  • Time — 合計時間です。planning (つまり、plan の作成 + plan の最適化 + パイプラインの構築) フェーズと execution (パイプラインの実行) フェーズに分けて表示されます。
  • Read — テーブルから読み取られた行数と非圧縮バイト数、および throughput です。これは通常のクエリのフッターで "Processed" として報告される数値と同じです。
  • Peak memory — クエリが使用したピークメモリです。

次に、クエリプランに表示される新しい行を見ていきましょう。

I/O: rows <in> → <out> (<selectivity>%) · <bytes_in> → <bytes_out>
  [Stage (<stage>): ]time <t> (<share>%) · parallelism <avg>/<max>

行数とバイト数は、ステップ全体について一度だけ報告されます (I/O 行) 。時間と並列度は、ステップ内の各ステージごとに、その下のインデントされた行に報告されます。

  • rows <in> → <out> — ステップに入力された行数と、ステップから出力された行数です。(<selectivity>%) は、そのステップがデータをどの程度絞り込んだか (out/in) 、または増やしたかを示します。入力行数と出力行数が同じ場合、および入力行数が 0 の場合は表示されません。
  • <bytes_in> → <bytes_out> — ステップ内を流れる非圧縮のインメモリバイト数です (両方ともゼロの場合は省略されます) 。
  • time <t> (<share>%) — そのステージがアクティブだった実時間と、クエリ実行時間に占める割合です (つまり、build time は含みません) 。ステージやステップは同時実行されるため、この割合の合計が 100% を超えることがあります。
  • parallelism <avg>/<max> — このステージ内で同時に動作していた CPU スレッド数の平均値と、そのステージで使用可能な最大値です。値が最大値に近いほど、そのステージは十分に並列化されていたことを示します。1 に近い場合は、ほぼ直列に実行されていたことを示します。
  • Stage (<stage>) — ステージ名です。ステージが 1 つだけのステップでは、Stage (...) ラベルは付かず、時間の行が直接出力されます。複数のステージを持つステップでは、各ステージごとにラベル付きの行が 1 行ずつ出力されます。たとえば Aggregating では Stage (partial aggregation)Stage (final aggregation) が表示され、ハッシュ結合では Stage (build)Stage (probe) が表示されます。

Join ステップ

join ステップでは、EXPLAIN ANALYZE は各側の参加行 (LeftRight) を出力し、その後に join の実装固有の行を出力します。LeftRight は、論理的な SQL の左右に対応します。ほとんどの場合、Left は join のプローブ側、Right はビルド側にもなります。ただし、join の実行中に swap が発生する可能性があるため、常にそうとは限りません。join_algorithm のすべての値 (hashparallel_hashgrace_hashpartial_mergefull_sorting_mergeparallel_full_sorting_mergedirect) に対応しています。また、この設定では選択できない 2 つの実装、すなわち CROSS または COMMA join、キーの等価条件を含まない ON 句、および Join テーブルエンジンも対象です。ほとんどの実装では両側が報告されますが、マテリアライズする側のみを報告するものもあります (たとえば、directLeft: のみを出力します) 。

各側の行は同じ形式です。

Left:  rows <left_rows>  · matched <matched_left_rows>  · match rate <match_rate>% · fanout <fanout>
Right: rows <right_rows> · matched <matched_right_rows> · match rate <match_rate>% · fanout <fanout>

各側について、EXPLAIN ANALYZE は以下を報告します。

  • rows <rows> — 結合処理を通過したその側の行の総数。
  • matched <matched_rows> — もう一方の側で少なくとも1つの結合相手が見つかった、その側の行数。これはキーではなくを数えます。キーが右側に3回出現して一致した場合、右側の3行すべてが一致した行としてカウントされます。
  • match rate <match_rate>% — 一致したその側の行の割合。100 * <matched_rows> / <rows> として算出されます。
  • fanout <fanout> — その側で一致した行1行あたりが平均して生成する出力行数。

正確に導出できない数値は、0 ではなく not collected として報告されます。match ratefanoutmatched から導出されるため、matched がない側では、3つすべてが not collected として報告されます。

ファンアウト

fanout は行数の増加倍率を示します。

matched output rows = <output_rows> - <NULL-padded rows of both sides>
fanout              = <matched output rows> / <matched_rows of that side>

外部結合では、相手が見つからなかった保持側の各行に対して、NULL で埋められた出力行が 1 行生成されます。これらの行は比率を薄めないよう差し引かれます。このような行が存在するのは保持側のみです。RIGHTFULL では右側、LEFTFULL では左側になります。

  • fanout = 0 — 一致した行からは出力行がまったく生成されません。これは ANTI join の動作であり、相手が見つからなかった行のみを出力します。
  • fanout = 1 — 明確な 1:1 join です。一致した各行から、ちょうど 1 行の出力行が生成されます。
  • fanout > 1 — 1:N join です。もう一方の側の重複キーによって行数が増加しました。両側で同時に大きな値になる場合は、意図しない Cartesian 的な行数の急増を示します。

数値に matches = 1 が必要な場合

これらの数値の大半は、結合がいずれにせよ構築するデータから得られるため、通常の EXPLAIN ANALYZE で報告されます。残りは、結合が通常は行わない追跡処理を必要とするため、EXPLAIN ANALYZE matches = 1 でのみ報告されます。どれが該当するかはアルゴリズムによって異なり、hash ファミリーでは次の 2 つです。

  • 一致したすべての右行に印を付ける必要がある、ALL INNERALL LEFT側。
  • ALL LEFTALL FULL側。ただし、クエリが右テーブルから何も選択せず、 ON セクションが単純なキーの等価条件である場合に限ります。それ以外の場合、プローブはすでに 一致した左行を記録します。これは右カラムをマテリアライズするため、または残余条件を評価するためです。 そのため、このオプションがなくてもカウントは正確です。

partial_merge では、同じ理由で 4 種類の ALL側に必要です。 full_sorting_mergeparallel_full_sorting_merge では、ANY 種類の両方の側に必要です。 ALL 種類では何も必要ありません。

matches = 1 を指定しても、すべての組み合わせで収集できるようになるわけではありません。結合がどちらの側を報告できるかは、その結合がいずれにせよ実行する必要がある処理によるため、種類や厳密性だけでなくアルゴリズムにも依存します。

Hash ファミリー。 hashparallel_hashgrace_hash は常に同じ結果になります。

Join matched matched
ALL INNER, ALL LEFT, ALL RIGHT, ALL FULL はい はい
SEMI LEFT, ANTI LEFT はい いいえ
ANY RIGHT, ANTI RIGHT いいえ はい
ASOF (inner) はい いいえ
SEMI RIGHT いいえ いいえ
ANY INNER, ANY LEFT, ASOF LEFT いいえ いいえ

結合がハッシュテーブル内でキーごとに 1 行だけを保持する場合、右側は収集できません。これは ANYSEMIANTI 結合で行われます。重複する右行は保存されないため、カウントできません。別の左行によってすでに相手が確保されている左行の出力を結合が抑制する場合、左側は収集できません。この場合、出力される行数は一致した行数より少なくなります。

any_join_distinct_right_table_keys を有効にすると、ANY は以前の RightAny セマンティクスに切り替わります。このセマンティクスでは左行ごとに 1 行が出力されるため、両方のカウントが保持されます。その場合、ANY RIGHTANY FULL両方の側を報告し、ANY INNERSEMI LEFT に書き換えられます。

Join テーブルエンジンも同じ表に従い、エンジンで宣言された種類と厳密性を使用します。Join(ALL, INNER, …) は両方の側を報告し、Join(ANY, LEFT, …) はどちらも報告しません。

マージアルゴリズム。 full_sorting_mergeparallel_full_sorting_merge は、4 種類の ALLANY INNERANY LEFTANY RIGHTASOFASOF LEFT を受け入れます。ASOFASOF LEFT を除くすべての種類で両方の側を報告します。これらでは右側が not collected になります。また、matches = 1 も不要です。2 つのソート済み入力を走査し、処理中に等しい範囲にあるすべての行を確認するため、後から再構築する必要がないからです。

partial_mergeALL INNERALL LEFTALL RIGHTALL FULLANY INNERANY LEFTSEMI LEFT を受け入れます。4 種類の ALL では両方の側を報告しますが、右側には matches = 1 が必要です。ANY INNERANY LEFTSEMI LEFT では右側が not collected になります。

direct 左側のみです。右側は行としてマテリアライズされないキー・バリューストアであるため、Right: 行自体がありません。

CROSSCOMMA、定数の ON 前述のとおり、どちらの側も報告されません。

両方のアルゴリズムが数値を報告する場合、その値は一致します。マージアルゴリズムは単により多くの情報を持っているだけで、何を一致とみなすかについて見解が異なるわけではありません。

アルゴリズム固有の行

各JOIN実装がこれに加えて出力する行を見てみましょう。

hash および parallel_hash JOIN、ならびに Join テーブルエンジンでは、Hash table: 行に右テーブルから構築されたハッシュテーブルの情報が表示されます。

Hash table: unique keys <unique_keys> · memory <peak_memory>
  • unique keys <unique_keys> — ビルドフェーズ中にハッシュテーブルに格納された一意のキーの数。
  • memory <peak_memory> — ビルドフェーズ中にハッシュテーブルが使用したピークメモリ。

grace_hash join では、Hash table: 行に、join がメモリ制限にどのように適応したかも表示されます。また、Spill: 行には、データがディスクにスピルされたかどうかが表示されます。

Hash table: unique keys <unique_keys> · memory <peak_memory> · buckets <buckets> · rehashes <rehashes>
Spill: yes · left spilled <left_spilled_bytes> · right spilled <right_spilled_bytes>
  • buckets <buckets> — 実行終了時点で Grace Hash Join に含まれるバケット数です。常に 2 のべき乗になります。
  • rehashes <rehashes> — メモリ制限内に収めるために、バケット数を 2 倍にする必要があった回数です。
  • Spill: — ディスクへのスピリングが発生したかどうかを示す yes/no フラグです。発生した場合、left spilled <left_spilled_bytes>right spilled <right_spilled_bytes> は、それぞれ左側 (probe) と右側 (build) でスピリングされた圧縮バイト数を示します。スピリングが発生しなかった場合、この行は単に Spill: no となります。

partial_merge 結合では、Right: 行に右テーブルのバッファリング方法とソート方法に関する追加情報が含まれ、ソート時間は Stage (build) 行と Stage (probe) 行に表示されます。

Right: rows <right_rows> · matched <matched_right_rows> · size <right_size> · blocks <right_blocks> · storage <in-memory|external> · match rate <match_rate>% · fanout <fanout>
  Stage (build): time <t> (<share>%) · parallelism <avg>/<max> · sort time <build_sort_time> · sort share <build_sort_share>%
  Stage (probe): time <t> (<share>%) · parallelism <avg>/<max> · sort time <probe_sort_time> · sort share <probe_sort_share>%
  • size <right_size> — 右テーブルのブロックのメモリ使用量。
  • blocks <right_blocks> — 右テーブルがバッファリングされたブロック数。
  • storage <in-memory|external> — 右テーブルがメモリに収まったか (in-memory) 、またはディスクにスピルする必要があったか (external) 。external の場合、追加の spilled <spilled_bytes> にディスクに書き込まれた圧縮バイト数が表示されます。
  • sort time <sort_time> — 右テーブルのソート (build ステージ) と、受信した各左ブロックのソート (probe ステージ) に費やされた時間。
  • sort share <sort_share>% — ステージの time の割合がクエリ全体の実行時間に占める割合であるのに対し、sort timeそのステージ自体のビジー時間 (各プロセッサの経過時間の合計) に占める割合。

full_sorting_merge join では、共通の Left: 行と Right: 行のみが出力されます。

direct join では、右側は行としてマテリアライズされず直接ルックアップされるキー・バリューストアであるため、Left: 行のみが出力されます。

CROSS または COMMA join、およびキーの等価条件を含まない任意の ON セクションでは、Buffer: 行に右テーブルがメモリ内でどのように保持されたかが示され、Spill: 行にディスクにスピルされたかどうかが表示されます。

Buffer: memory <peak_memory> · compressed <yes|no>
Spill: yes · right spilled <right_spilled_bytes>
  • memory <peak_memory> — バッファリングされた右テーブルが使用したピークメモリ。
  • compressed <yes|no> — バッファリングされたブロックが少なくとも1つ圧縮されているかどうか。圧縮されている場合、reader は保存されているすべてのブロックを展開します。
  • Spill:grace_hash と同じ yes/no フラグで、right spilled <right_spilled_bytes> はディスクに書き込まれた圧縮バイト数を示します。

ここでは両側で matched not collected が報告されます。定数の predicate では、すべての左行がすべての右行と組み合わされるか、まったく組み合わされないかのいずれかとなるため、個々のどの行が一致したかを特定することはできません。

Join テーブルエンジンとの join では、事前構築済みテーブルを説明する Hash table: 行とともに、両側が報告されます。右側では、クエリごとの build の行数ではなく、エンジンに格納されている行数がカウントされます。

プロセッサごとの時間

processors = 1 の場合、各ステージの下に追加の行が出力され、そのステージのプロセッサごとの経過時間の分布が表示されます。

Time per processor (<n>): min <t> · median <t> · max <t> · sum <t>

<n> はそのステージのプロセッサ数です。medianmax の間に大きなギャップがある場合は、並列プロセッサ間で負荷に偏りがあることを示します。

EXPLAIN ESTIMATE

クエリの実行時に、テーブルから読み取られると推定される行数、マーク数、パーツ数を表示します。MergeTree ファミリーのテーブルで使用できます。

テーブルを作成します。

Querysql
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;
Querysql
EXPLAIN ESTIMATE SELECT * FROM ttt;
Responsetext
┌─database─┬─table─┬─parts─┬─rows─┬─marks─┐
│ default  │ ttt   │     1 │  128 │     8 │
└──────────┴───────┴───────┴──────┴───────┘

EXPLAIN WHATIF

仮想的なスキップ索引をディスク上にマテリアライズすることなく、それが SELECT クエリにもたらす効果を見積もります。CREATE HYPOTHETICAL INDEX で 1 つ以上の候補を定義し、EXPLAIN WHATIF SELECT ... を実行すると、各候補について、適用可否、推定読み取りマーク数、推定バイト数、スキップ率を確認できます。

構文

EXPLAIN WHATIF [empirical = 0] SELECT ...

設定

  • empirical1 (デフォルト) では、スキップ率 (上限値) を測定するため、ベースラインで絞り込まれたグラニュールに対してメモリ内で索引を適用します。0 ではその処理をスキップします。いずれの場合も、empirical で結果が得られない場合 (無効になっている、または索引をメモリ内で評価できない場合) 、推定器はカラム STATISTICS にフォールバックし、それも利用できなければ、最終的に適用可否のみのサマリーにフォールバックします。

出力

Baseline (after PK + partition + existing indexes):
  table:       db.t
  parts:       1
  marks:       100
  est_bytes:   1.50 MiB             (only when the query reads rows)

With idx_b (minmax, hypothetical):
  status:       applicable
  marks:        1
  est_bytes:    15.00 KiB           (only when baseline bytes are known)
  skip_ratio:   99.0%

Estimation:
  source:           empirical | statistical | applicability_only
  empirical_status: ok | unsupported | disabled
  empirical_reason: <reason>        (only when empirical_status = unsupported)
  sampled_parts:    50 / 100        (only when source = empirical)
  sampled_marks:    50 / 100        (only when source = empirical)
  elapsed_us:       631             (only when source = empirical)
  • source — 推定値の算出方法を示します。
    • empirical: ベースラインで pruned されたグラニュールを対象に、メモリ内で索引を構築し、その索引によってスキップされるグラニュール数を数えます。これは上限値です。制限事項については CREATE HYPOTHETICAL INDEX を参照してください。
    • statistical: カラム STATISTICS から導出されます。empirical が無効化されている場合 (empirical = 0) 、または empirical で結果を生成できず、かつ関連するカラムにカラム STATISTICS が定義されている場合に使用されます。
    • applicability_only: 索引は predicate に適用可能ですが、empirical と statistical のいずれでも結果を生成できなかったことを示します (たとえば empirical = 0 でカラム STATISTICS が定義されていない場合) 。保守的な上限として skip_ratio: 0.0% を返します。
  • empirical_reason — 経験的推定を実行できなかった理由です。empirical_status: unsupported の場合にのみ表示されます。たとえば、merge_tree_min_rows_for_seek または merge_tree_min_bytes_for_seek がゼロ以外の場合、実際の読み取りではマーク範囲が統合されますが、グラニュールごとのカウントではこれをモデル化しないため、推定は statistical または applicability_only にフォールバックします。
  • sampled_parts / sampled_marks<baseline-pruned> / <total in the table>。テーブル全体のうち、PK、partition、既存の索引による pruning を通過した割合、つまり仮想索引への入力となる部分を示します。
  • est_bytes — 読み取られるバイト数の推定値です。テーブルの平均行サイズから導出されるため概算であり、ストレージや圧縮によって変動します。ベースラインの行はクエリが行を読み取る場合にのみ表示され、候補ごとの行はベースラインのバイト推定値がわかっている場合にのみ表示されます。

この設定は WHATIFSELECT の間にインラインで記述します。SETTINGS キーワードはありません (これは、他の EXPLAIN バリアントでオプションを受け付ける方法と一致しています) 。

テーブルに仮想索引が定義されていない場合、EXPLAIN WHATIFstatus: not_applicable を返し、作成を促すヒントを表示します。

結合行 (複数候補)

2 つ以上の候補が empirical で評価されると、EXPLAIN WHATIF は候補ごとの行の後に (combined: idx_a, idx_b, ...) という名前の追加ブロックを 1 つ付加します。これは、それらすべての索引を同時に持つ場合の総合的な効果を示します。実際の読み取りでは、あるグラニュールが保持されるのは、それが すべて のスキップ索引を通過した場合だけなので、結合推定値は各候補で生き残るグラニュールの積集合になります。したがって、この skip_ratio は少なくとも最良の単一候補と同等以上になります。相補的な索引は組み合わせることでより多くを prune し、冗長な索引では変化しません。

寄与するのは source: empirical の候補だけです。これは、結合行が各 グラニュール ごとの生存集合の積集合を取って構築されるためです。statistical または applicability_only と推定された候補には グラニュール ごとのデータがないため除外されます。その結果、結合ブロックが表示されるのは少なくとも 2 つの候補が経験的推定を生成した場合だけで、それ以外の場合 (たとえば empirical = 0 の場合) には省略されます。その推定フィールドは、elapsed_us0 である点を除き、候補ごとの経験則ベースのブロックと同じです — 結合推定は候補ごとのスキャンから導出されるものであり、新たなスキャンではありません。合成された (combined: ...) という名前はレポート用ラベルにすぎず、force_data_skipping_indices では使用できません。

経験則ベースの例

CREATE TABLE t (a UInt64, b UInt64) ENGINE = MergeTree ORDER BY a
SETTINGS index_granularity = 100;

INSERT INTO t SELECT number, number FROM numbers(10000);

CREATE HYPOTHETICAL INDEX idx_b ON t (b) TYPE minmax GRANULARITY 1;

EXPLAIN WHATIF SELECT * FROM t WHERE b = 42;
Baseline (after PK + partition + existing indexes):
  table:       default.t
  parts:       1
  marks:       100
  est_bytes:   85.52 KiB

With idx_b (minmax, hypothetical):
  status:       applicable
  marks:        1
  est_bytes:    875.00 B
  skip_ratio:   99.0%

Estimation:
  source:           empirical
  empirical_status: ok
  sampled_parts:    1 / 1
  sampled_marks:    100 / 100

仮に minmax を使うと、100 個のマークを 1 個まで絞り込めます — skip_ratio: 99.0%。(est_bytes は平均行サイズに基づく推定値のため、正確な値は変動します。)

統計の例

カラム STATISTICSはデフォルトで無効になっています。statistical パスを試すには、まず対象のカラムでこれらを定義し、materialize mutation が完了するまで待ちます:

ALTER TABLE t ADD STATISTICS b TYPE tdigest;
ALTER TABLE t MATERIALIZE STATISTICS b SETTINGS mutations_sync = 1;

次に、推定器がカラム STATISTICS にフォールバックするよう、経験則ベースのパスを無効にします:

EXPLAIN WHATIF empirical = 0 SELECT * FROM t WHERE b < 10;
With idx_b (minmax, hypothetical):
  status:       applicable
  marks:        1
  est_bytes:    1.66 KiB
  skip_ratio:   99.9%

Estimation:
  source:           statistical
  empirical_status: disabled

この数値は、b < 10 のカラム STATISTICS における選択性 (10000 行中およそ 10 行) に基づくもので、skip_ratio の上限として報告されます。sampled_parts / sampled_marks はなく、データは読み取られていません。

どちらの方法も利用できない場合 (たとえば empirical = 0 で、かつカラム STATISTICS が定義されていない場合) 、推定器は source: applicability_only と保守的な skip_ratio: 0.0% を報告します。

EXPLAIN TABLE OVERRIDE

テーブル関数を介してアクセスするテーブルのスキーマに対して、テーブルオーバーライドを適用した結果を表示します。 また、いくつかの検証も行い、オーバーライドによって何らかの問題が発生する場合は例外をスローします。

次のようなリモート MySQL テーブルがあるとします。

Querysql
CREATE TABLE db.tbl (
    id INT PRIMARY KEY,
    created DATETIME DEFAULT now()
)
Querysql
EXPLAIN TABLE OVERRIDE mysql('127.0.0.1:3306', 'db', 'tbl', 'root', 'clickhouse')
PARTITION BY toYYYYMM(assumeNotNull(created))
Responsetext
┌─explain─────────────────────────────────────────────────┐
│ PARTITION BY uses columns: `created` Nullable(DateTime) │
└─────────────────────────────────────────────────────────┘
Navigation