SQL 执行计划的展示是了解 SQL 执行逻辑和性能调优的最重要的手段。用户可以通过 Explain 命令查看优化器针对给定SQL 生成的逻辑执行计划。在执行 Explain 命令时,系统会为给定的 SQL 生成最终的逻辑执行计划并展示给用户,但并不会生成相应的物理执行计划(可执行的代码),也不会真正执行该计划或者将该计划放入计划缓存。

由于Explain并不会真正执行给定的 SQL,用户一般可以放心使用该功能而不用担心在性能调试中可能给系统性能带来影响。也正是因为没有真正执行 SQL,Explain 所展示的计划是在执行命令时优化器根据当前的用户输入和数据统计信息所生成的逻辑执行计划,而并不是在计划缓存中真正被使用的物理执行计划(二者在某些场景下可能会有不同)。展示计划缓存中的物理计划的方式请参考实时执行计划展示章节。

Explain命令格式

Explain 命令格式如下例所示,展示的格式包括 BASIC、EXTENDED、PARTITIONS 等等,内容的详细程度有所区别。

  1. EXPLAIN [BASIC | EXTENDED | PARTITIONS | FORMAT = format_name] explainable_stmt
  2. format_name: { TRADITIONAL | JSON }
  3. explainable_stmt: { SELECT statement
  4. | DELETE statement
  5. | INSERT statement
  6. | REPLACE statement
  7. | UPDATE statement }

计划形状与算子信息

Explain 输出的第一部分是执行计划的树形结构展示。其中每一个操作在树中的层次通过其在 OPERATOR 中的缩进予以展示。如下例所示的执行计划:

  1. |==========================================
  2. |ID|OPERATOR |NAME|EST. ROWS|COST|
  3. ------------------------------------------
  4. |0 |SORT | |1 |2763|
  5. |1 | MERGE INNER JOIN| |1 |2735|
  6. |2 | SORT | |1000 |1686|
  7. |3 | TABLE SCAN |t2 |1000 |1180|
  8. |4 | TABLE SCAN |t1 |1 |815 |
  9. ==========================================

其对应的树状执行计划如下图所示:

image

表格中的各项的含义如下:

列名含义
ID执行树按照前序遍历的方式得到的编号(从0开始)
OPERATOR操作算子的名称
NAME对应表操作的表名(索引名)
EST. ROWS估算的该操作算子的输出行数
COST该操作算子的执行代价(微秒)

在表操作中,NAME 字段会显示该操作涉及的表的名称(别名),如果是使用索引访问,还会在名称后的括号中展示该索引的名称, 例如 t1(t1_c2) 表示使用了 t1_c2 这个索引。另外,如果扫描的顺序是逆序,还会在后面使用 reserve 关键字标识,例如 t1(t1_c2,Reverse)。

一些常见的算子类型归纳如下表:

类型算子
表访问table scan, table get
连接NESTED-LOOP, BLK-NESTED-LOOP,Merge、hash
排序sort,top-n sort
聚合merge group-by,hash group-by, window function
分布式exchange in/out remote/distribute
集合union, except, intersect,minus
其他limit, material, subplan, expression, count

操作算子详细输出

Explain 输出的第二部分是各操作算子的详细信息,包括输出表达式、过滤条件、分区信息以及各算子的独有信息,包括排序键、连接键、下压条件等。

  1. Outputs & filters:
  2. -------------------------------------
  3. 0 - output([t1.c1], [t1.c2], [t2.c1], [t2.c2]), filter(nil), sort_keys([t1.c1, ASC], [t1.c2, ASC]), prefix_pos(1)
  4. 1 - output([t1.c1], [t1.c2], [t2.c1], [t2.c2]), filter(nil),
  5. equal_conds([t1.c1 = t2.c2]), other_conds(nil)
  6. 2 - output([t2.c1], [t2.c2]), filter(nil), sort_keys([t2.c2, ASC])
  7. 3 - output([t2.c2], [t2.c1]), filter(nil),
  8. access([t2.c2], [t2.c1]), partitions(p0)
  9. 4 - output([t1.c1], [t1.c2]), filter(nil),
  10. access([t1.c1], [t1.c2]), partitions(p0)

重点的信息如下:

  • output
    在执行树中每一个算子都需要向上游算子输出一些表达式以供后面的计算,也叫做投影操作,这里列出了需要进行投影的表达式(包括普通列)。

  • filters
    OB 的所有算子都有执行过滤条件的能力,filters 列出了该算子需要执行的过滤操作,对于表访问操作,我们会将所有过滤条件(包括回表前后的过滤条件都下压到存储层)。

  • access
    表访问操作中需要调用存储层的接口访问具体的数据,access 展示了存储层的对外输出(投影)列名。

  • is_index_back 和 filter_before_indexback
    OceanBase 区分了哪些过滤条件可以在索引回表前执行,哪些需要在回表后执行,这个标示可以通过filter_before_indexback 得到。如果表操作所有的filter都可以再回表前执行,则 is_index_back 为 true,否则为 false。

  • partitions
    OceanBase 内部支持二级分区。针对用户 SQL 给定的条件,优化器会将不需要访问的分区过滤掉,这一步骤我们称为分区裁剪。partitions 显式了经过分区裁剪后剩下的分区,其中如果涉及到多个连续分区,例如从分区0到分区20,会按照“起始分区号——结束分区号”的形式展示,例如 partitions(p0-20)。

  • range_key 和 range
    OceanBase 中的物理表理论上都是索引组织表(包括二级索引本身),扫描的时候都会按照一定的顺序进行扫面,这个顺序就是该表的主键,体现在 range_key 中,当用户给定不同条件时,优化器会定位到最终扫描的范围,通过 range 展示,如下例所示的计划:
  1. OceanBase (root@oceanbase)> explain extended
  2. select /*+ index(t1 t1_c2) */* from t1 where c3 = 5 and c1 = 6 order by c2, c3;
  3. ========================================
  4. |ID|OPERATOR |NAME |EST. ROWS|COST|
  5. ----------------------------------------
  6. |0 |TABLE SCAN|t1(t1_c2)|1 |1255|
  7. ========================================
  8. Outputs & filters:
  9. -------------------------------------
  10. 0 - output([t1.c1(0x7f1d520a0a98)], [t1.c2(0x7f1d520a0d98)], [t1.c3(0x7f1d5209fbe0)]), filter([t1.c3(0x7f1d5209fbe0) = 5(0x7f1d5209f5d8)], [t1.c1(0x7f1d520a0a98) = 6(0x7f1d520a0490)]),
  11. access([t1.c3(0x7f1d5209fbe0)], [t1.c1(0x7f1d520a0a98)], [t1.c2(0x7f1d520a0d98)]), partitions(p0),
  12. is_index_back=true, filter_before_indexback[false,true],
  13. range_key([t1.c2(0x7f1d520a0d98)], [t1.c1(0x7f1d520a0a98)]), range(MIN,MIN ; MAX,MAX)always true

可以看出要访问的表为 t1_c2 这张索引表,表的主键为 (c2, c1),扫描的范围是全表扫描。

  • sort_keys
    排序操作的排序键,包括其排序方向。

一些常用的信息含义如下表:

信息名称含义
output该算子的输出表达式列表
filter该算子执行的过滤条件
access表访问中存储层的投影列名
partitions分区裁剪的信息
sort_keys排序操作的排序键
prefix_pos局部有序的偏移位置
equal_conds连接操作中执行的等值连接条件
other_conds连接操作中执行的非等值连接条件