> ## Documentation Index
> Fetch the complete documentation index at: https://private-7c7dfe99-trino-dialect.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

> EXPLAIN 文档

# EXPLAIN 语句

显示语句的执行计划。

<div class="vimeo-container">
  <Frame>
    <iframe
      src="//www.youtube.com/embed/hP6G2Nlz_cA"
      frameborder="0"
      allow="autoplay;
fullscreen;
picture-in-picture"
      allowfullscreen
    />
  </Frame>
</div>

语法：

```sql theme={null}
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 ...]
```

示例：

```sql theme={null}
EXPLAIN SELECT sum(number) FROM numbers(10) UNION ALL SELECT sum(number) FROM numbers(10) ORDER BY sum(number) ASC FORMAT TSV;
```

```sql theme={null}
Output: sum(number)

Union
├──Aggregating
│  │  Keys:
│  │  Aggregates: sum(number)
│  │  Skip merging: 0
│  └──ReadFromSystemNumbers
│        Output: number
└──Sorting (Sorting for ORDER BY)
   │  Sort description: sum(number) ASC
   └──Aggregating
      │  Keys:
      │  Aggregates: sum(number)
      │  Skip merging: 0
      └──ReadFromSystemNumbers
            Output: number
```

<div id="explain-types">
  ## EXPLAIN 类型
</div>

* `AST` — 抽象语法树。
* `SYNTAX` — 经 AST 层级优化后的查询文本。
* `QUERY TREE` — 经查询树层级优化后的查询树。
* `PLAN` — 查询执行计划。
* `PIPELINE` — 查询执行流水线。
* `ANALYZE` — 执行查询，并用测得的运行时指标为执行计划添加注释。
* `ESTIMATE` — 处理查询时，将从表中读取的预估行数、标记数和 parts 数量。
* `TABLE OVERRIDE` — 表函数 schema 上表覆盖的已验证结果。

<div id="explain-ast">
  ### EXPLAIN AST
</div>

转储查询的 AST。支持所有类型的查询，不仅限于 `SELECT`。

设置：

* `graph` – 以 [DOT](https://en.wikipedia.org/wiki/DOT_\(graph_description_language\)) 图描述语言定义的图形式输出 AST。默认值：0。

示例：

```sql theme={null}
EXPLAIN AST SELECT 1;
```

```sql theme={null}
SelectWithUnionQuery (children 1)
 ExpressionList (children 1)
  SelectQuery (children 1)
   ExpressionList (children 1)
    Literal UInt64_1
```

```sql theme={null}
EXPLAIN AST ALTER TABLE t1 DELETE WHERE date = today();
```

```sql theme={null}
  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
```

<div id="explain-syntax">
  ### EXPLAIN SYNTAX
</div>

显示查询在语法分析后的抽象语法树 (AST) 。

其过程是：解析查询、构建查询 AST 和查询树，并可选择运行查询分析器和优化阶段，然后再将查询树转换回查询 AST。

设置：

* `oneline` – 以单行形式输出查询。默认值：`0`。
* `run_query_tree_passes` – 在转储查询树之前运行查询树处理阶段。默认值：`0`。
* `query_tree_passes` – 如果设置了 `run_query_tree_passes`，则指定要运行的处理阶段数量。若未指定 `query_tree_passes`，则会运行所有处理阶段。
* `single_record` – 将格式化后的查询作为单个多行记录返回，而不是每行一个记录。默认值：`1` (由 `explain_syntax_single_record` 设置控制) 。设置为 `0` 可恢复历史的每行一个记录输出，也可以将 `explain_syntax_single_record = 0` (全局或在每个查询的 `SETTINGS` 中) 设置为 `0`，或将 `compatibility` 设置为早于 `26.8` 的任意版本。

示例：

```sql title="Query" theme={null}
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;
```

```sql title="Response" theme={null}
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` 时：

```sql title="Query" theme={null}
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;
```

```sql title="Response" theme={null}
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
```

<div id="explain-query-tree">
  ### EXPLAIN QUERY TREE
</div>

设置：

* `run_passes` — 在转储查询树之前运行所有查询树处理阶段。默认值：`1`。
* `dump_passes` — 在转储查询树之前，先转储已使用处理阶段的信息。默认值：`0`。
* `passes` — 指定要运行的处理阶段数量。如果设置为 `-1`，则运行所有处理阶段。默认值：`-1`。
* `dump_tree` — 显示查询树。默认值：`1`。
* `dump_ast` — 显示根据查询树生成的查询 AST。默认值：`0`。

示例：

```sql theme={null}
EXPLAIN QUERY TREE SELECT id, value FROM test_table;
```

```sql theme={null}
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
```

<div id="explain-plan">
  ### EXPLAIN PLAN
</div>

转储查询计划步骤。

设置：

* `optimize` — 控制在显示查询计划之前是否应用查询计划优化。默认值：1。
* `header` — 打印步骤的输出请求头。默认值：0。
* `description` — 打印步骤说明。默认值：1。
* `indexes` — 显示使用到的索引、每个已应用索引过滤掉的 parts 数量，以及过滤掉的粒度数量。默认值：0。支持 [MergeTree](/zh/reference/engines/table-engines/mergetree-family/mergetree) 表。从 ClickHouse >= v25.9 开始，此语句只有在配合 `SETTINGS use_query_condition_cache = 0, use_skip_indexes_on_data_read = 0` 使用时，才会显示合理的输出。
* `projections` — 显示所有已分析的投影，以及它们基于投影主键条件对 part 级过滤的影响。对于每个投影，此部分都会包含统计信息，例如通过投影主键评估的 parts、行、标记和范围数量。它还会显示由于这种过滤而跳过了多少 data parts，而无需实际从投影本身读取数据。某个投影是否实际用于读取，还是仅用于过滤分析，可通过 `description` 字段判断。默认值：0。支持 [MergeTree](/zh/reference/engines/table-engines/mergetree-family/mergetree) 表。
* `actions` — 打印步骤操作的详细信息。默认值：1。
* `sorting` — 为每个生成有序输出的计划步骤打印排序说明。默认值：0。
* `keep_logical_steps` — 对 joins 保留逻辑计划步骤，而不是将其转换为物理 join 实现。默认值：0。
* `json` — 以 [JSON](/zh/reference/formats/JSON/JSON) 格式将查询计划步骤打印为一行。默认值：0。建议使用 [TabSeparatedRaw (TSVRaw)](/zh/reference/formats/TabSeparated/TabSeparatedRaw) 格式，以避免不必要的转义。
* `input_headers` — 打印步骤的输入请求头。默认值：0。通常仅对开发者调试输入输出请求头不匹配相关问题时有用。
* `column_structure` — 除列名和类型外，还会打印请求头中列的结构。默认值：0。通常仅对开发者调试输入输出请求头不匹配相关问题时有用。
* `distributed` — 显示在远程节点上为分布式表或并行副本执行的查询计划。不支持与 `json` 一起使用。默认值：0。
* `compact` — 启用后，会在计划中隐藏表达式步骤以及详细操作信息 (输入、函数、别名和输出位置) 。仅在 `actions = 1` 时生效。默认值：1。
* `pretty` — 使用线框字符 (├──、└──、│) 而不是缩进来打印计划树，以便更直观地展示层级结构。还会以内联方式格式化 join 步骤属性。默认值：1。

<Note>
  默认情况下，`explain_query_plan_default = 'pretty'`，因此 `actions`、`compact` 和 `pretty` 都会初始化为 `1`，并且计划会以紧凑、美观且带操作注释的形式呈现。在 `EXPLAIN` 语句中显式指定这些选项中的任意一个 (例如 `EXPLAIN actions = 0, compact = 0, pretty = 0 SELECT ...`) 始终会覆盖默认值。

  在 ClickHouse 26.7 之前，`actions`、`compact` 和 `pretty` 的默认值为 `0`。你仍然可以通过将 `explain_query_plan_default = 'legacy'` (全局设置或在每个查询的 `SETTINGS` 中设置) ，或将 `compatibility` 设置为任何早于 `26.7` 的版本，来获得该输出。

  即使设置了 `explain_query_plan_default = 'pretty'`，`json` 和 `distributed` 选项也不会启用 `pretty` 的默认值 (`actions`、`compact` 和 `pretty`) 。若要在其输出中包含操作详情，请手动设置 `actions = 1`。
</Note>

示例：

```sql theme={null}
EXPLAIN SELECT sum(number) FROM numbers(10) GROUP BY number % 4  LIMIT 1;
```

```sql theme={null}
Output: sum(number)

Limit (preliminary LIMIT)
│  Limit 1
│  Offset 0
└──Aggregating
   │  Keys: number MOD 4
   │  Aggregates: sum(number)
   │  Skip merging: 0
   └──ReadFromSystemNumbers
         Output: number
```

<Note>
  不支持步骤和查询成本估算。
</Note>

当 `json = 1` 时，查询计划会以 JSON 格式表示。每个节点都是一个字典，并且始终包含 `Node Type`、`Node Id` 和 `Plans` 键。`Node Type` 是表示步骤名称的字符串，`Node Id` 是唯一的步骤标识符 (步骤名称加上数字后缀，例如 `Union_10`) 。`Plans` 是一个包含子步骤描述的数组。根据节点类型和设置，还可能会添加其他可选键。

示例：

```sql theme={null}
EXPLAIN json = 1, description = 0 SELECT 1 UNION ALL SELECT 2 FORMAT TSVRaw;
```

```json theme={null}
[
  {
    "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` 键会添加到该步骤中：

```json theme={null}
{
  "Node Type": "ReadFromStorage",
  "Description": "SystemOne"
}
```

当 `header` = 1 时，`Header` 键会以列数组的形式添加到该步骤中。

示例：

```sql theme={null}
EXPLAIN json = 1, description = 0, header = 1 SELECT 1, 2 + dummy;
```

```json theme={null}
[
  {
    "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-Max`、`Partition`、`Statistics`、`PrimaryKey` 或 `Skip`) 以及以下可选键：

* `Name` — 索引名称 (目前仅用于 `Skip` 索引) 。
* `Keys` — 该索引使用的列数组。
* `Condition` — 使用的条件。
* `Description` — 索引描述 (目前仅用于 `Skip` 索引) 。
* `Parts` — 应用该索引后/前的 parts 数量。
* `Granules` — 应用该索引后/前的粒度数量。
* `Ranges` — 应用该索引后的粒度范围数量。

示例：

```json theme={null}
"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` 键。它包含一个已分析的 projections 数组。每个 projection 以 JSON 格式描述，包含以下键：

* `Name` — 投影名称。
* `Condition` — 使用的投影主键条件。
* `Description` — 关于投影如何使用的描述 (例如 part 级过滤) 。
* `Selected Parts` — 该投影选中的 parts 数量。
* `Selected Marks` — 选中的标记数量。
* `Selected Ranges` — 选中的范围数量。
* `Selected Rows` — 选中行数。
* `Filtered Parts` — 由于 part 级过滤而跳过的 parts 数量。

示例：

```json theme={null}
"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 时，添加的键取决于步骤类型。

示例：

```sql theme={null}
EXPLAIN json = 1, actions = 1, description = 0 SELECT 1 FORMAT TSVRaw;
```

```json theme={null}
[
  {
    "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` 步骤以及表达式的详细信息：

```sql theme={null}
EXPLAIN actions = 1, compact = 0 SELECT sum(number) FROM numbers(10) GROUP BY number % 4;
```

```text theme={null}
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 时，输出不仅包含本地查询计划，还包含将在远程节点上执行的查询计划。这对于分析和调试分布式查询非常有用。

<Note>
  `distributed` 仅以 legacy (非 `pretty`) 格式呈现，因为 `pretty` 输出不会将远程分片的计划整合到计划树中。因此，启用 `distributed` 会自动禁用 `pretty` 的默认选项 (`actions`、`compact` 和 `pretty`) ，无论 `explain_query_plan_default` 如何设置。你仍然可以手动设置 `actions=1`。`distributed` 选项也不支持与 `json` 一起使用。
</Note>

分布式表示例：

```sql theme={null}
EXPLAIN distributed=1 SELECT * FROM remote('127.0.0.{1,2}', numbers(2)) WHERE number = 1;
```

```sql theme={null}
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
```

并行副本示例：

```sql theme={null}
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;
```

```sql theme={null}
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 记法显示 (例如，使用 `a + 1 > 5` 而不是 `greater(plus(a, 1), 5)`) 。为便于理解，内部列标识符前缀 (例如 `__table1.`) 会被移除。
* **源步骤** (例如 `ReadFromMergeTree`) 会显示其输出列。
* **过滤步骤** 会以 SQL 记法显示过滤条件。存在运行时 join 过滤器时，它们会单独显示。
* **聚合步骤** 会显示键以及聚合函数及其参数 (例如 `sum(c)`、`count()`) 。
* 来自元组字面量的 **IN 集合** 会显示其值 (大型集合会被截断) ，基于子查询的集合会标记为 `subquery1`、`subquery2` 等，而来自 `Set` engine 表的集合会显示表名。
* **Join 步骤** 会使用数学记法显示 join 关系、预估结果行数，
  以及哪些输出列来自左侧或右侧。以下符号用于
  表示不同的 JOIN types：

| 符号                     | Join 类型 |
| ---------------------- | ------- |
| `⋈`                    | 内连接     |
| `⟕`                    | 左连接     |
| `⟖`                    | 右连接     |
| `⟗`                    | 全连接     |
| `⋉`                    | 左半连接    |
| `⋊`                    | 右半连接    |
| `⋉` with strikethrough | 左反连接    |
| `⋊` with strikethrough | 右反连接    |
| `×`                    | 交叉连接    |

例如，`t1 ⟕ t2` 表示表 `t1` 与 `t2` 之间的左连接。
表名后方括号中的数字 (例如 `t1[100]`) 表示预估行数，
前提是表统计信息可用。

`pretty` 选项与 `compact = 1` 搭配使用效果很好，它会隐藏 `Expression` 步骤和详细的动作信息，使执行计划更易于阅读。

一个详细的 JOIN 示例：

```sql theme={null}
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;
```

```text theme={null}
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
```

<div id="explain-pipeline">
  ### EXPLAIN PIPELINE
</div>

设置：

* `header` — 为每个输出端口打印请求头。默认值：0。
* `graph` — 打印以 [DOT](https://en.wikipedia.org/wiki/DOT_\(graph_description_language\)) 图描述语言表示的图。默认值：0。
* `compact` — 如果启用了 `graph` 设置，则以紧凑模式打印图。默认值：1。
* `compact_repeated_processor_chains` — 在文本输出中，通过显示一份事件链副本及其重复次数，将相邻的重复处理器事件链进行紧凑表示。当同一事件链多次出现时 (例如在 joins 中) ，这可以让并行管道更易于阅读。它不会影响图输出。默认值：0。

```text theme={null}
Resize 16 → 1
  FillingRightJoinSide          │
    SimpleSquashingTransform    │ × 16
      Resize 1 → 16
```

当 `compact=0` 且 `graph=1` 时，处理器名称会包含一个额外的后缀，用于标明处理器的唯一标识符。

示例：

```sql theme={null}
EXPLAIN PIPELINE SELECT sum(number) FROM numbers_mt(100000) GROUP BY number % 4;
```

```sql theme={null}
(Union)
(Expression)
ExpressionTransform
  (Expression)
  ExpressionTransform
    (Aggregating)
    Resize 2 → 1
      AggregatingTransform × 2
        (Expression)
        ExpressionTransform × 2
          (SettingQuotaAndLimits)
            (ReadFromStorage)
            NumbersRange × 2 0 → 1
```

<div id="explain-analyze">
  ### EXPLAIN ANALYZE
</div>

`EXPLAIN ANALYZE` 会实际执行查询，丢弃结果行，并输出与 `EXPLAIN PLAN` 相同的计划树，同时为每个步骤标注其在运行时的实际执行情况。

设置：

`EXPLAIN ANALYZE` 接受与 `EXPLAIN PLAN` 相同的显示选项 (见 [EXPLAIN PLAN](#explain-plan) 小节) 。

* `header` — 见 [EXPLAIN PLAN](#explain-plan) 小节。
* `description` — 见 [EXPLAIN PLAN](#explain-plan) 小节。
* `projections` — 见 [EXPLAIN PLAN](#explain-plan) 小节。
* `sorting` — 见 [EXPLAIN PLAN](#explain-plan) 小节。
* `input_headers` — 见 [EXPLAIN PLAN](#explain-plan) 小节。
* `column_structure` — 见 [EXPLAIN PLAN](#explain-plan) 小节。
* `actions` — 见 [EXPLAIN PLAN](#explain-plan) 小节。默认值：1。
* `indexes` — 见 [EXPLAIN PLAN](#explain-plan) 小节。默认值：1。
* `compact` — 见 [EXPLAIN PLAN](#explain-plan) 小节。默认值：1。
* `pretty` — 见 [EXPLAIN PLAN](#explain-plan) 小节。默认值：1。
* `processors` — 对于 `EXPLAIN ANALYZE`，会为每个阶段额外输出一行，显示各处理器耗时的分布：`min`、`median`、`max` 和 `sum`。这有助于发现并行处理器之间的负载不均。默认值：0。
* `matches` — 对于 `EXPLAIN ANALYZE`，当无法从 join 产生的结果中推导出 `matched`、`match rate` 和 `fanout` 指标时，会使 join 步骤执行生成这些指标所需的额外记录工作。可以推导出时，无需此选项也会报告这些指标。请参见 [Join steps](#explain-analyze-join-steps)。默认值：0。

<Note>
  由于 `EXPLAIN ANALYZE` 会实际执行被包装的查询，因此它在多个方面的行为都与该
  查询本身一致 —— 而不同于不会执行查询的 `EXPLAIN` 形式：

  * **Quotas and limits.** 它会计入相同的 [quotas](/zh/concepts/features/configuration/server-config/quotas)
    ，并受相同的 [limits](/zh/concepts/features/configuration/settings/query-complexity)
    约束 (例如 `query_selects`、`read_rows`) ，就像直接运行该查询一样。在规划期间免于配额计费的数据源
    (例如 `system.one`) 不会被计费。
  * **Failed transactions.** 在已经失败 (`ROLLED_BACK`) 的 [transaction](/zh/concepts/features/operations/insert/transactions)
    内部，它会像普通 `SELECT` 一样因 `INVALID_TRANSACTION` 被拒绝 —— 请先执行 `ROLLBACK`。
  * **Streaming reads.** 对于流式 (`FROM ... STREAM`) 读取，它会因
    `NOT_IMPLEMENTED` 被拒绝，因为这类读取永远不会完成。
  * **Distributed queries.** 对于在
    [Distributed](/zh/reference/engines/table-engines/special/distributed) 模式下执行的查询，不支持使用它。
</Note>

示例：

```sql theme={null}
EXPLAIN ANALYZE SELECT number % 10 AS k, count() FROM numbers_mt(1000000) GROUP BY k;
```

```text theme={null}
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
```

我们来看看输出。首先看一下请求头。

```txt theme={null}
   Query summary:
     Time:        <total> (planning <planning> · execution <execution>)
     Read:        <rows> rows, <bytes> (<rows/s>, <bytes/s>)
     Peak memory: <peak>
```

* `Time` — 总时间，分为规划阶段 (即创建计划 + 优化计划 + 构建管道) 和执行阶段 (运行管道) 。
* `Read` — 从表中读取的行数和未压缩字节数，以及吞吐量 —— 与普通查询 footer 中显示为 "Processed" 的数字相同。
* `Peak memory` — 查询使用的峰值内存占用。

现在我们来看看查询计划中新出现的这些行。

```txt theme={null}
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 时间) 。请注意，由于阶段和步骤会并发运行，这些占比相加可能超过 100%。
* `parallelism <avg>/<max>` — 该阶段内同时工作的 CPU 线程平均数量，以及它可使用的最大数量。数值接近最大值表示该阶段并行化良好；接近 1 则表示它大多是串行运行的。
* `Stage (<stage>)` — 阶段名称。只有单个阶段的步骤会直接打印时间行，而不带 `Stage (...)` 标签。包含多个阶段的步骤则会为每个阶段分别打印一行带标签的内容，例如 `Aggregating` 会显示 `Stage (partial aggregation)` 和 `Stage (final aggregation)`，而 hash 连接 会显示 `Stage (build)` 和 `Stage (probe)`。

<Note>
  ClickHouse 不仅会并行执行计划步骤内的任务，也会并行执行各个计划步骤。`parallelism` 指标仅反映此步骤本身的工作情况。其他步骤也可能同时运行，因此这个数值并不能体现该步骤的并行度相对于整个查询的情况。
</Note>

<Note>
  `parallelism` 中的最大值取以下两者的较小值：

  1. 计划步骤中的任务总数；
  2. `max_threads` 中设置的最大查询处理线程数。
</Note>

<div id="explain-analyze-join-steps">
  #### Join 步骤
</div>

对于 JOIN 步骤，`EXPLAIN ANALYZE` 会输出每一侧的*参与*行——`Left` 和 `Right`——后面跟着所有特定于该 JOIN 实现的行。`Left` 和 `Right` 对应 SQL 中的逻辑左右侧。在大多数情况下，`Left` 也是 JOIN 的探测侧，`Right` 则是 JOIN 的构建侧。不过，由于 JOIN 执行期间可能发生交换，情况并非总是如此。涵盖了 [`join_algorithm`](/zh/reference/settings/session-settings/join#join_algorithm) 的所有取值 (`hash`、`parallel_hash`、`grace_hash`、`partial_merge`、`full_sorting_merge`、`parallel_full_sorting_merge`、`direct`) ，以及该设置无法选择的两种实现：`CROSS` 或 `COMMA` JOIN、任何不含键相等条件的 `ON` 子句，以及 [`Join`](/zh/reference/engines/table-engines/special/join) 表引擎。大多数实现都会报告两侧；有些仅报告其物化的一侧 (例如，`direct` 只输出 `Left:`) 。

各侧的行采用相同的形态：

```txt theme={null}
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>` — 该侧经过 JOIN 的行总数。
* `matched <matched_rows>` — 该侧在另一侧找到至少一个 JOIN 匹配行的行数。这里统计的是*行*，而不是键：如果某个键在右侧出现三次且能够匹配，则这三行右侧行都会计为已匹配。
* `match rate <match_rate>%` — 该侧已匹配行的占比，计算方式为 `100 * <matched_rows> / <rows>`。
* `fanout <fanout>` — 该侧平均每个已匹配行产生的输出行数。

无法精确推导的数值会报告为 `not collected`，而不是 `0`。`match rate` 和 `fanout` 都由 `matched` 推导而来，因此没有 `matched` 的一侧会将这三项均报告为 `not collected`。

<div id="explain-analyze-fanout">
  #### 扇出
</div>

`fanout` 用于衡量行数的倍增情况：

```txt theme={null}
matched output rows = <output_rows> - <NULL-padded rows of both sides>
fanout              = <matched output rows> / <matched_rows of that side>
```

外JOIN会为保留侧中每个未找到匹配行的行输出一个以 `NULL` 填充的结果行。这些行会被扣除，以免拉低该比率。只有保留侧会出现此类行——`RIGHT` 和 `FULL` 为右侧，`LEFT` 和 `FULL` 为左侧：

* `fanout = 0` — 匹配行完全没有产生结果行，这正是 `ANTI` JOIN的行为：它只输出未找到匹配行的行。
* `fanout = 1` — 完整的 1:1 JOIN；每个匹配行恰好产生一个结果行。
* `fanout > 1` — 1:N JOIN；另一侧的重复键导致行数倍增。两侧同时出现较大值，表明发生了非预期的 Cartesian 膨胀。

<div id="explain-analyze-matches">
  #### 何时需要 `matches = 1` 才能获取数值
</div>

这些数值大多来自 JOIN 本身就会生成的数据，普通的 `EXPLAIN ANALYZE` 即会报告。其余数值需要执行 JOIN 原本不会进行的额外记录操作，因此只有在使用 `EXPLAIN ANALYZE matches = 1` 时才会报告。具体哪些数值属于这种情况取决于算法；对于哈希家族，有以下两种情况：

* `ALL INNER` 和 `ALL LEFT` 的**右**侧，因为需要标记每个匹配的右侧行；
* `ALL LEFT` 和 `ALL FULL` 的**左**侧，但仅限于查询未从
  右表选择任何内容，且 `ON` 子句只是简单的键相等条件时。否则，探测过程已经记录了
  哪些左侧行匹配——无论是为了物化右侧列，还是为了评估剩余
  条件——因此即使不使用该选项，计数也完全准确。

出于相同原因，`partial_merge` 对四种 `ALL` 类型的**右**侧需要此选项。
`full_sorting_merge` 和 `parallel_full_sorting_merge` 对 `ANY` 类型的**两**侧需要此选项。`ALL`
类型则无需额外操作。

<Note>
  该选项默认关闭，因为额外的记录操作并非没有成本，代价是测量保真度。相关工作在探测循环中完成，且会随输出行数增加而增长。需要准确了解左侧行和右侧行各自在另一侧找到多少匹配项时，请使用 `matches = 1`。
</Note>

`matches = 1` 并不能让所有组合都可收集。JOIN 能报告哪一侧，取决于该 JOIN 本身必须执行哪些操作，因此既取决于算法，也取决于类型和严格性。

**哈希家族。** `hash`、`parallel_hash` 和 `grace_hash` 的行为始终一致：

| Join                                             | 匹配的左侧 | 匹配的右侧 |
| ------------------------------------------------ | ----- | ----- |
| `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`             | 否     | 否     |

当 JOIN 在哈希表中每个键只保留一行时，右侧便不可收集；`ANY`、`SEMI` 和 `ANTI` JOIN 正是如此：重复的右侧行从不存储，因此无法计数。当 JOIN 会抑制输出其匹配行已被另一个左侧行占用的左侧行时，左侧便不可收集，因为输出的行数会少于实际匹配的行数。

启用 [`any_join_distinct_right_table_keys`](/zh/reference/settings/session-settings/other#any_join_distinct_right_table_keys) 会将 `ANY` 切换为旧版 `RightAny` 语义，即每个左侧行输出一行，因此可保留两个计数。此时，`ANY RIGHT` 和 `ANY FULL` 会报告**两**侧，而 `ANY INNER` 会被重写为 `SEMI LEFT`。

[`Join`](/zh/reference/engines/table-engines/special/join) 表引擎遵循相同的表格规则，并使用引擎中声明的类型和严格性：`Join(ALL, INNER, …)` 报告两侧，`Join(ANY, LEFT, …)` 则两侧均不报告。

**合并算法。** `full_sorting_merge` 和 `parallel_full_sorting_merge` 支持四种 `ALL` 类型、`ANY INNER`、`ANY LEFT`、`ANY RIGHT`、`ASOF` 和 `ASOF LEFT`。除 `ASOF` 和 `ASOF LEFT` 外，它们会为每种类型报告**两**侧；对于后两者，右侧为 `not collected`，且无需 `matches = 1`——它们会遍历两个已排序的输入，并在处理相等范围时看到其中的每一行，因此之后无需重建任何信息。

`partial_merge` 支持 `ALL INNER`、`ALL LEFT`、`ALL RIGHT`、`ALL FULL`、`ANY INNER`、`ANY LEFT` 和 `SEMI LEFT`。它会为四种 `ALL` 类型报告两侧，其中右侧需要 `matches = 1`；对于 `ANY INNER`、`ANY LEFT` 和 `SEMI LEFT`，右侧为 `not collected`。

**`direct`。** 仅报告左侧。右侧是从不物化为行的键值存储，因此根本没有 `Right:` 行。

**`CROSS`、`COMMA` 和常量 `ON`。** 两侧均不报告，如[上文](#explain-analyze-join-algorithm-lines)所述。

当两种算法都报告数值时，结果是一致的。合并算法只是掌握了更多信息；对于何谓匹配，它们并无分歧。

<div id="explain-analyze-join-algorithm-lines">
  #### 算法特定行
</div>

下面来看各 JOIN 实现额外添加的行。

对于 `hash` 和 `parallel_hash` JOIN 以及 `Join` 表引擎，`Hash table:` 行描述了根据右表构建的哈希表：

```txt theme={null}
Hash table: unique keys <unique_keys> · memory <peak_memory>
```

* `unique keys <unique_keys>` — 构建阶段哈希表中存储的唯一键数量。
* `memory <peak_memory>` — 构建阶段哈希表的峰值内存占用。

对于 `grace_hash` JOIN，`Hash table:` 行还会说明该 JOIN 如何适应内存限制，`Spill:` 行则会说明数据是否已落盘：

```txt theme={null}
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>` — 为满足内存限制而将桶数量翻倍的次数。
* `Spill:` — 一个 `yes`/`no` 标志，表示是否发生了落盘。若发生落盘，`left spilled <left_spilled_bytes>` 和 `right spilled <right_spilled_bytes>` 分别报告左侧 (probe) 和右侧 (build) 落盘的压缩字节数；若未发生落盘，该行即为 `Spill: no`。

对于 `partial_merge` JOIN，`Right:` 行会提供右表缓冲和排序方式的额外信息，排序时间显示在 `Stage (build)` 和 `Stage (probe)` 行中：

```txt theme={null}
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>` — 对右表排序 (在构建阶段) 以及对每个传入的左块排序 (在探测阶段) 所花费的时间。
* `sort share <sort_share>%` — `sort time` 占*该阶段自身忙碌时间* (其处理器耗时之和) 的比例；不同于阶段 `time` 的百分比，后者占*整个查询执行时间*的比例。

对于 `full_sorting_merge` JOIN，仅输出通用的 `Left:` 和 `Right:` 行。

对于 `direct` JOIN，仅输出 `Left:` 行，因为右侧是直接查找的键值存储，而非物化为行。

对于 `CROSS` 或 `COMMA` JOIN，以及任何不含键相等条件的 `ON` 部分，`Buffer:` 行会说明右表如何保存在内存中，`Spill:` 行则报告其是否落盘：

```txt theme={null}
Buffer: memory <peak_memory> · compressed <yes|no>
Spill: yes · right spilled <right_spilled_bytes>
```

* `memory <peak_memory>` — 缓冲右表占用的峰值内存。
* `compressed <yes|no>` — 是否至少有一个缓冲块经过压缩；读取器随后会解压所有已存储的块。
* `Spill:` — 与 `grace_hash` 相同的 `yes`/`no` 标志；`right spilled <right_spilled_bytes>` 则报告落盘的压缩字节数。

此处两侧均显示 `matched not collected`：常量谓词要么让每个左行与每个右行匹配，要么完全不匹配，因此无法确定具体哪些行发生了匹配。

对于与 [`Join`](/zh/reference/engines/table-engines/special/join) 表引擎进行的 JOIN，会同时报告两侧的信息，并附带描述预构建表的 `Hash table:` 行。右侧统计的是存储在引擎中的行数，而非某次查询中构建的行数。

<div id="explain-analyze-processors">
  #### 每个处理器的耗时
</div>

当 `processors = 1` 时，会在每个阶段下方额外打印一行，显示该阶段各处理器的耗时分布：

```txt theme={null}
Time per processor (<n>): min <t> · median <t> · max <t> · sum <t>
```

`<n>` 是该阶段中的处理器数量。`median` 和 `max` 之间差距较大，说明并行处理器之间存在负载不均。

<div id="explain-estimate">
  ### EXPLAIN ESTIMATE
</div>

显示在处理查询时，将从表中读取的预估行数、标记数和 parts 数量。适用于 [MergeTree](/zh/reference/engines/table-engines/mergetree-family/mergetree) 家族的表。

**示例**

创建表：

```sql title="Query" theme={null}
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;
```

```sql title="Query" theme={null}
EXPLAIN ESTIMATE SELECT * FROM ttt;
```

```text title="Response" theme={null}
┌─database─┬─table─┬─parts─┬─rows─┬─marks─┐
│ default  │ ttt   │     1 │  128 │     8 │
└──────────┴───────┴───────┴──────┴───────┘
```

<div id="explain-whatif">
  ### EXPLAIN WHATIF
</div>

在*不*将索引物化到磁盘上的情况下，估算假设性跳过索引对 `SELECT` 查询的潜在收益。使用 [`CREATE HYPOTHETICAL INDEX`](/zh/reference/statements/hypothetical-index#create-hypothetical-index) 定义一个或多个候选项，然后运行 `EXPLAIN WHATIF SELECT ...`，即可查看每个候选项的以下信息：是否适用、预估读取的标记数、预估字节数以及跳过比率。

**语法**

```sql theme={null}
EXPLAIN WHATIF [empirical = 0] SELECT ...
```

**设置**

* `empirical` — `1` (默认值) 会在内存中对经过基线剪枝的粒度运行索引，以测量跳过比例 (上限) 。`0` 则会跳过该路径。无论哪种情况，如果 empirical 未产生结果 (已禁用，或索引无法在内存中求值) ，估算器都会回退到列[统计信息](/zh/reference/engines/table-engines/mergetree-family/mergetree#column-statistics)；如果两者都不可用，则最终回退到仅包含适用性的摘要。

**输出**

```text theme={null}
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
  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`：在内存中基于经过基线剪枝后的粒度构建索引，并统计该索引原本可以跳过的粒度数。这是一个上界——请参阅 [`CREATE HYPOTHETICAL INDEX`](/zh/reference/statements/hypothetical-index#limitations) 中的限制。
  * `statistical`：根据列统计信息推导得出。当经验估算被禁用 (`empirical = 0`) ，或经验估算无法得出结果，且相关列已定义列统计信息时使用。
  * `applicability_only`：该索引适用于该谓词，但经验估算和统计估算都未得出结果 (例如 `empirical = 0` 且未定义列统计信息) 。作为保守上界，会报告 `skip_ratio: 0.0%`。
* `sampled_parts` / `sampled_marks` — `<baseline-pruned> / <total in the table>`。显示在经过 PK、分区和现有索引剪枝后，表中有多少比例的数据被保留下来，即假设索引的输入。
* `est_bytes` — 读取字节数的估算值，由表的平均行大小推导而来，因此只是近似值，并会随存储和压缩情况而变化。只有当查询读取行时才会显示 baseline 行；只有在已知 baseline 字节估算值时，才会显示各候选项对应的行。

该设置以内联方式写在 `WHATIF` 与 `SELECT` 之间——没有 `SETTINGS` 关键字 (这与其他 `EXPLAIN` 变体接受选项的方式一致) 。

如果该表没有定义任何假设索引，`EXPLAIN WHATIF` 会报告 `status: not_applicable`，并提示你创建一个。

**组合行 (多个候选项) **

当以经验方式评估两个或更多候选项时，`EXPLAIN WHATIF` 会在各候选项对应的行后追加一个名为 `(combined: idx_a, idx_b, ...)` 的额外块。它报告的是同时拥有*所有*这些索引时的联合收益：实际读取只有在某个粒度通过了*每个*跳过索引时才会保留该粒度，因此组合估算就是各候选项保留下来的粒度的交集。因此，它的 `skip_ratio` 至少与表现最佳的单个候选项一样高——互补的索引一起会剪枝掉更多数据，而冗余的索引则不会带来变化。

只有 `source: empirical` 的候选项会参与计算，因为组合行是通过对它们各自按粒度划分的存活集合求交集构建出来的。估算为 `statistical` 或 `applicability_only` 的候选项没有按粒度划分的数据，因此会被排除；因此，只有当至少两个候选项生成了经验估算时，组合块才会出现，否则将被省略 (例如在 `empirical = 0` 时) 。它的估算字段与单个候选项的经验块相同，但 `elapsed_us` 为 `0`——组合估算是根据各候选项的扫描结果推导得出的，而不是一次新的扫描。合成的 `(combined: ...)` 名称仅用作报告标签，不能与 `force_data_skipping_indices` 一起使用。

**经验示例**

```sql theme={null}
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;
```

```text theme={null}
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` 是根据平均行大小估算得出的，因此具体数值会有所不同。)

**统计示例**

列[统计信息](/zh/reference/engines/table-engines/mergetree-family/mergetree#column-statistics)默认处于关闭状态。要触发 `statistical` 路径，请先在相关列上定义统计信息，然后等待物化变更完成：

```sql theme={null}
ALTER TABLE t ADD STATISTICS b TYPE TDigest;
ALTER TABLE t MATERIALIZE STATISTICS b SETTINGS mutations_sync = 1;
```

然后禁用经验路径，这样估算器就会退回到使用列统计信息：

```sql theme={null}
EXPLAIN WHATIF empirical = 0 SELECT * FROM t WHERE b < 10;
```

```text theme={null}
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` 的列统计信息选择性 (10000 行中约 10 行) ，并以 `skip_ratio` 上界的形式给出。不存在 `sampled_parts` / `sampled_marks`——未读取任何数据。

如果这两种路径都不可用 (例如 `empirical = 0` 且未定义列统计信息) ，估算器会报告 `source: applicability_only`，以及保守的 `skip_ratio: 0.0%`。

<div id="explain-table-override">
  ### EXPLAIN TABLE OVERRIDE
</div>

显示通过 table function 访问的表 schema 上应用表覆盖后的结果。
还会进行一些验证；如果该覆盖会导致某种失败，则会抛出异常。

**示例**

假设你有一个如下所示的远程 MySQL 表：

```sql title="Query" theme={null}
CREATE TABLE db.tbl (
    id INT PRIMARY KEY,
    created DATETIME DEFAULT now()
)
```

```sql title="Query" theme={null}
EXPLAIN TABLE OVERRIDE mysql('127.0.0.1:3306', 'db', 'tbl', 'root', 'clickhouse')
PARTITION BY toYYYYMM(assumeNotNull(created))
```

```text title="Response" theme={null}
┌─explain─────────────────────────────────────────────────┐
│ PARTITION BY uses columns: `created` Nullable(DateTime) │
└─────────────────────────────────────────────────────────┘
```

<Note>
  验证并不完整，因此查询成功也不能保证该覆盖不会导致问题。
</Note>
