TiDB 执行计划概览
注意
使用 MySQL 客户端连接到 TiDB 时,为避免输出结果在终端中换行,可先执行 pager less -S
命令。执行命令后,新的 EXPLAIN
的输出结果不再换行,可按右箭头 → 键水平滚动阅读输出结果。
使用 EXPLAIN
可查看 TiDB 执行某条语句时选用的执行计划。也就是说,TiDB 在考虑上数百或数千种可能的执行计划后,最终认定该执行计划消耗的资源最少、执行的速度最快。
EXPLAIN
示例如下:
返回的结果如下:
EXPLAIN
实际不会执行查询。EXPLAIN ANALYZE 可用于实际执行查询并显示执行计划。如果 TiDB 所选的执行计划非最优,可用 EXPLAIN
或 EXPLAIN ANALYZE
来进行诊断。有关 EXPLAIN
用法的详细内容,参阅以下文档:
EXPLAIN
的返回结果包含以下字段:
id
为算子名,或执行 SQL 语句需要执行的子任务。详见。estRows
为显示 TiDB 预计会处理的行数。该预估数可能基于字典信息(例如访问方法基于主键或唯一键),或基于CMSketch
或直方图等统计信息估算而来。task
显示算子在执行语句时的所在位置。详见 Task 简介。access-object
显示被访问的表、分区和索引。显示的索引为部分索引。以上示例中 TiDB 使用了a
列的索引。尤其是在有组合索引的情况下,该字段显示的信息很有参考意义。operator info
显示访问表、分区和索引的其他信息。详见 。
在执行计划返回结果中,自 v6.4.0 版本起,特定算子(即 IndexJoin
和 Apply
算子的 Probe 端所有子节点)的 estRows
字段意义与 v6.4.0 版本之前的有所不同。
在 v6.4.0 之前,estRows
表示对于 Build 端子节点的每一行,Probe 端预计会处理的行数。自 v6.4.0 起,estRows
表示 Probe 端预计会处理的总行数。由于 EXPLAIN ANALYZE
中展示的实际行数( 列)表示的是总行数,v6.4.0 起这些算子 estRows
的含义与 actRows
列的含义保持一致。
例如:
算子是为返回查询结果而执行的特定步骤。真正执行扫表(读盘或者读 TiKV Block Cache)操作的算子有如下几类:
- TableFullScan:全表扫描。
- TableRangeScan:带有范围的表数据扫描。
- TableRowIDScan:根据上层传递下来的 RowID 扫描表数据。时常在索引读操作后检索符合条件的行。
- IndexFullScan:另一种“全表扫描”,扫的是索引数据,不是表数据。
- IndexRangeScan:带有范围的索引数据扫描操作。
TiDB 会汇聚 TiKV/TiFlash 上扫描的数据或者计算结果,这种“数据汇聚”算子目前有如下几类:
- TableReader:将 TiKV 上底层扫表算子 TableFullScan 或 TableRangeScan 得到的数据进行汇总。
- IndexLookUp:先汇总 Build 端 TiKV 扫描上来的 RowID,再去 Probe 端上根据这些
RowID
精确地读取 TiKV 上的数据。Build 端是IndexFullScan
或IndexRangeScan
类型的算子,Probe 端是TableRowIDScan
类型的算子。 - IndexMerge:和
IndexLookupReader
类似,可以看做是它的扩展,可以同时读取多个索引的数据,有多个 Build 端,一个 Probe 端。执行过程也很类似,先汇总所有 Build 端 TiKV 扫描上来的 RowID,再去 Probe 端上根据这些 RowID 精确地读取 TiKV 上的数据。Build 端是IndexFullScan
或IndexRangeScan
类型的算子,Probe 端是TableRowIDScan
类型的算子。
算子的执行顺序
算子的结构是树状的,但在查询执行过程中,并不严格要求子节点任务在父节点之前完成。TiDB 支持同一查询内的并行处理,即子节点“流入”父节点。父节点、子节点和同级节点可能并行执行查询的一部分。
在以上示例中,├─IndexRangeScan_8(Build)
算子为 a(a)
索引所匹配的行查找内部 RowID。└─TableRowIDScan_9(Probe)
算子随后从表中检索这些行。
Build 总是先于 Probe 执行,并且 Build 总是出现在 Probe 前面。即如果一个算子有多个子节点,子节点 ID 后面有 Build 关键字的算子总是先于有 Probe 关键字的算子执行。TiDB 在展现执行计划的时候,Build 端总是第一个出现,接着才是 Probe 端。
范围查询
若要使用索引,条件必须是 “Sargable” (Search ARGument ABLE) 的。例如条件 YEAR(date_column) < 1992
不能使用索引,但 date_column < '1992-01-01
就可以使用索引。
推荐使用同一类型的数据以及同一类型的进行比较,以避免引入额外的 cast
操作而导致不能利用索引。
可以在范围查询条件中使用 AND
(求交集)和 OR
(求并集)进行组合。对于多维组合索引,可以对多个列使用条件。例如对组合索引 :
- 当
a
为等值查询时,可以继续求b
的查询范围。 - 当
b
也为等值查询时,可以继续求c
的查询范围。 - 反之,如果
a
为非等值查询,则只能求a
的范围。
目前 TiDB 的计算任务分为两种不同的 task:cop task 和 root task。Cop task 是指使用 TiKV 中的 Coprocessor 执行的计算任务,root task 是指在 TiDB 中执行的计算任务。
SQL 优化的目标之一是将计算尽可能地下推到 TiKV 中执行。TiKV 中的 Coprocessor 能支持大部分 SQL 内建函数(包括聚合函数和标量函数)、SQL LIMIT
操作、索引扫描和表扫描。
EXPLAIN
返回结果中 operator info
列可显示诸如条件下推等信息。本文以上示例中,operator info
结果各字段解释如下:
range: [1,1]
表示查询的WHERE
字句 (a = 1
) 被下推到了 TiKV,对应的 task 为cop[tikv]
。keep order:false
表示该查询的语义不需要 TiKV 按顺序返回结果。如果查询指定了排序(例如SELECT * FROM t WHERE a = 1 ORDER BY id
),该字段的返回结果为keep order:true
。stats:pseudo
表示estRows
显示的预估数可能不准确。TiDB 定期在后台更新统计信息。也可以通过执行ANALYZE TABLE t
来手动更新统计信息。
EXPLAIN
执行后,不同算子返回不同的信息。你可以使用 Optimizer Hints 来控制优化器的行为,以此控制物理算子的选择。例如 表示优化器将使用 Hash Join 算法。详细内容见 Optimizer Hints。
TiDB 在 MySQL 的基础上,定义了一些专用的系统变量和语法用来优化性能。其中一些系统变量和具体的算子相关,比如算子的并发度,算子的内存使用上限,是否允许使用分区表等。这些都可以通过系统变量进行控制,从而影响各个算子执行的效率。