15.OB4.0执行计划中算子

2.TABLE LOOKU

TABLE LOOKUP 算子用于表示全局索引的回表逻辑
create table t2(c1 int primary key,c2 int,c3 int) partition by hash(c1) partitions 4;
create index i1 on t2(c2) global ;
explain select * from t2 where c2=1\G;
obclient [sjzt]> explain select * from t2 where c2=1\G;
*************************** 1. row ***************************
Query Plan: ==================================================
|ID|OPERATOR               |NAME  |EST. ROWS|COST|
--------------------------------------------------
|0 |TABLE LOOKUP           |t2    |1        |28  |
|1 | DISTRIBUTED TABLE SCAN|t2(i1)|1        |2   |
==================================================

Outputs & filters: 
-------------------------------------
  0 - output([t2.c1], [t2.c2], [t2.c3]), filter(nil), rowset=256, 
      partitions(p[0-3])
  1 - output([t2.c1], [calc_partition_tablet_id(t2.c1)]), filter(nil), rowset=256, 
      access([t2.c1]), partitions(p0)

1 row in set (0.120 sec)
#先按i1索引进行索引扫描,索引扫描找不到的值需要回表查询。
#用到了i1索引。
#ID=1对p0分区扫描,ID=0,所有分区扫描。


上述示例中,1 号算子是扫描全局索引 i1,0 号算子表明从主表中获取不在全局索引的列。
执行计划展示中的 outputs & filters 详细展示了 
TABLE LOOKUP 算子的输出信息如下
信息名称	含义
output	该算子的输出列。
filter	该算子的过滤谓词。 由于示例中 TABLE LOOKUP 算子没有设置 filter,所以为 nil。即没有过滤谓词。
partitions	查询需要扫描的分区。

3.JOIN

JOIN 算子用于将两张表的数据,按照特定的条件进行联接。
JOIN 的类型主要包括内联接(Inner Join)、外联接(Outer Join)和半联接(Semi/Anti Join)三种。

OceanBase 数据库支持的 JOIN 算子主要有:NESTED LOOP JOIN (NLJ)、MERGE JOIN (MJ) 和 HASH JOIN (HJ)
这个和Oracle一样,Oracle主要也是支持:NLJ,MJ,HJ; SQLSERVER也是这三种连接方法:NLJ,MJ,HJ; 

NESTED LOOP JOIN (NLJ)
如下示例中,Q1 和 Q2 查询使用 Hint 指定了查询使用 NLJ。
其中,0 号算子是一个 NLJ 算子。这个算子存在两个子节点,分别是 1 号算子和 4 号算子,它的执行逻辑为:
从 1 号算子读取一行。
打开 4 号算子,读取所有的行。
联接接 1和 4 号算子的输出结果,并执行过滤条件,输出结果。
重复第一步,直到 1 号算子迭代结束

drop table t1; 
drop table t2; 
create table t1(c1 int,c2 int);
create table t2(d1 int,d2 int,primary key(d1));
explain select /*+USE_NL(t1,t2)*/ t1.c2+t2.d2 from t1,t2 
where c2=d2\G;

obclient [sjzt]> explain select /*+USE_NL(t1,t2)*/ t1.c2+t2.d2 from t1,t2 
    -> where c2=d2\G;
*************************** 1. row ***************************
Query Plan: =================================================
|ID|OPERATOR            |NAME    |EST. ROWS|COST|
-------------------------------------------------
|0 |NESTED-LOOP JOIN    |        |1        |4   |
|1 | PX COORDINATOR     |        |1        |3   |
|2 |  EXCHANGE OUT DISTR|:EX10000|1        |3   |
|3 |   TABLE SCAN       |t1      |1        |2   |
|4 | MATERIAL           |        |1        |2   |
|5 |  TABLE SCAN        |t2      |1        |2   |
=================================================

Outputs & filters: 
-------------------------------------
  0 - output([t1.c2 + t2.d2]), filter(nil), rowset=256, 
      conds([t1.c2 = t2.d2]), nl_params_(nil)
  1 - output([t1.c2]), filter(nil), rowset=256
  2 - output([t1.c2]), filter(nil), rowset=256, is_single, dop=1
  3 - output([t1.c2]), filter(nil), rowset=256, 
      access([t1.c2]), partitions(p0)
  4 - output([t2.d2]), filter(nil), rowset=256
  5 - output([t2.d2]), filter(nil), rowset=256, 
      access([t2.d2]), partitions(p0)

1 row in set (0.012 sec)

其中, MATERIAL 算子用于物化下层算子输出的数据,详细信息请参见 MATERIAL 
explain select /*+USE_NL(t1,t2)*/ t1.c2+t2.d2 from t1,t2 where c1=d1\G;

#Q2
obclient [sjzt]> explain select /*+USE_NL(t1,t2)*/ t1.c2+t2.d2 from t1,t2 
    -> where c1=d1\G;
*************************** 1. row ***************************
Query Plan: ==================================================
|ID|OPERATOR             |NAME    |EST. ROWS|COST|
--------------------------------------------------
|0 |NESTED-LOOP JOIN     |        |1        |5   |
|1 | TABLE SCAN          |t2      |1        |2   |
|2 | MATERIAL            |        |1        |3   |
|3 |  PX COORDINATOR     |        |1        |3   |
|4 |   EXCHANGE OUT DISTR|:EX10000|1        |3   |
|5 |    TABLE SCAN       |t1      |1        |2   |
==================================================

Outputs & filters: 
-------------------------------------
  0 - output([t1.c2 + t2.d2]), filter(nil), rowset=256, 
      conds([t1.c1 = t2.d1]), nl_params_(nil)
  1 - output([t2.d1], [t2.d2]), filter(nil), rowset=256, 
      access([t2.d1], [t2.d2]), partitions(p0)   #未分区,不是分区表,只有p0;
  2 - output([t1.c1], [t1.c2]), filter(nil), rowset=256
  3 - output([t1.c1], [t1.c2]), filter(nil), rowset=256
  4 - output([t1.c1], [t1.c2]), filter(nil), rowset=256, is_single, dop=1   #并行度是1;
  5 - output([t1.c1], [t1.c2]), filter(nil), rowset=256, 
      access([t1.c1], [t1.c2]), partitions(p0)

1 row in set (0.003 sec)


#上述示例中,执行计划展示中的 outputs & filters 详细展示了 NESTED LOOP JOIN 算子的具体输出信息如下
信息名称	含义
output	该算子输出的表达式。
filter	该算子上的过滤条件。 由于示例中 NLJ 算子没有设置 filter,所以为 nil。
conds	联接条件。 
例如 Q1 查询中 t1.c2 = t2.d2 联接条件。nl_params_	根据 NLJ 左表的数据产生的下推参数。 
例如 Q2 查询中的 t1.c1 , NLJ 在迭代到左表的每一行时,都会根据 nl_params 构造一个参数,
根据这个参数和原始的联接条件 c1 = d1 ,构造一个右表上的过滤条件: d1 = ?。 
这个过滤条件会下推到右表上,并抽取索引上的查询范围,即需要扫描索引哪个范围的数据。
在 Q2 查询中,由于存在下推条件 d1 = ?,所以 2 号算子是 TABLE GET 算子。

如下示例中,
Q3 查询中没有指定任何的联接条件,0 号算子展示成了一个 NESTED-LOOP JOIN CARTESIAN 
逻辑上它还是一个 NLJ 算子,代表一个没有任何联接条件的 NLJ。
#Q3
explain select t1.c2+t2.d2 from t1,t2 \G;
obclient [sjzt]> explain select t1.c2+t2.d2 from t1,t2 \G;
*************************** 1. row ***************************
Query Plan: =======================================================
|ID|OPERATOR                  |NAME    |EST. ROWS|COST|
-------------------------------------------------------
|0 |NESTED-LOOP JOIN CARTESIAN|        |1        |4   |
|1 | PX COORDINATOR           |        |1        |3   |
|2 |  EXCHANGE OUT DISTR      |:EX10000|1        |3   |
|3 |   TABLE SCAN             |t1      |1        |2   |
|4 | MATERIAL                 |        |1        |2   |
|5 |  TABLE SCAN              |t2      |1        |2   |
=======================================================

Outputs & filters: 
-------------------------------------
  0 - output([t1.c2 + t2.d2]), filter(nil), rowset=256, 
      conds(nil), nl_params_(nil)    #没有关联条件,就是笛卡尔积。
  1 - output([t1.c2]), filter(nil), rowset=256
  2 - output([t1.c2]), filter(nil), rowset=256, is_single, dop=1
  3 - output([t1.c2]), filter(nil), rowset=256, 
      access([t1.c2]), partitions(p0)
  4 - output([t2.d2]), filter(nil), rowset=256
  5 - output([t2.d2]), filter(nil), rowset=256, 
      access([t2.d2]), partitions(p0)

1 row in set (0.002 sec)


MERGE JOIN (MJ)
如下示例中,Q4 查询使用 USE_MERGE 的 Hint 指定了查询使用 MJ。
其中,0 号算子是一个 MJ 算子,它有两个子节点,分别是 1 和 5 号算子。
该算子会对左右子节点的数据进行归并联接,因此,要求左右子节点的数据相对于联接列是有序的。
以 Q4 查询为例,联接条件为 t1.c2 = t2.d2,它要求表 t1 的数据是按照 c2 列排序的,表 t2 的数据是按照 d2 列排序的。
在 Q4 查询中,2 号算子的输出是无序的;
4 号算子的输出是按照 d2 列排序的,均不满足 MJ 对序的要求,因此,分配了 1 和 3 号算子进行排序。

#Q4
explain select /*+USE_MERGE(t1,t2)*/ t1.c2+t2.d2 from t1,t2 
where c2=d2 and c1+d1>10\G;
obclient [sjzt]> explain select /*+USE_MERGE(t1,t2)*/ t1.c2+t2.d2 from t1,t2 
    -> where c2=d2 and c1+d1>10\G;
*************************** 1. row ***************************
Query Plan: =================================================
|ID|OPERATOR            |NAME    |EST. ROWS|COST|
-------------------------------------------------
|0 |MERGE JOIN          |        |1        |5   |
|1 | PX COORDINATOR     |        |1        |3   |
|2 |  EXCHANGE OUT DISTR|:EX10000|1        |3   |
|3 |   SORT             |        |1        |2   |
|4 |    TABLE SCAN      |t1      |1        |2   |  #t1 表上没有索引,所以需要排序。
|5 | SORT               |        |1        |2   |
|6 |  TABLE SCAN        |t2      |1        |2   |    #t2表d1上有索引,索引是有序的,所以不需要排序。
=================================================

Outputs & filters: 
-------------------------------------
  0 - output([t1.c2 + t2.d2]), filter(nil), rowset=256, 
      equal_conds([t1.c2 = t2.d2]), other_conds([t1.c1 + t2.d1 > 10])
  1 - output([t1.c2], [t1.c1]), filter(nil), rowset=256
  2 - output([t1.c2], [t1.c1]), filter(nil), rowset=256, is_single, dop=1
  3 - output([t1.c2], [t1.c1]), filter(nil), rowset=256, sort_keys([t1.c2, ASC])
  4 - output([t1.c2], [t1.c1]), filter(nil), rowset=256, 
      access([t1.c2], [t1.c1]), partitions(p0)
  5 - output([t2.d2], [t2.d1]), filter(nil), rowset=256, sort_keys([t2.d2, ASC])
  6 - output([t2.d1], [t2.d2]), filter(nil), rowset=256, 
      access([t2.d1], [t2.d2]), partitions(p0)

1 row in set (0.003 sec)
如下示例中,Q5 查询中联接条件是 t1.c1 = t2.d1 ,它要求表 t1 的数据是按照 c1 列排序的,表 t2 的数据是按照 d1 列排序的。
在这个执行计划中,表 t2 选择了主表扫描,结果是按照 d1 列排序的,因此不需要额外分配一个 SORT 算子。
理想情况下,JOIN 的左右表选择了合适的索引,索引提供的数据顺序能够满足 MJ 的要求,此时不需要分配任何 SORT 算子。

explain select /*+USE_MERGE(t1,t2)*/ t1.c2+t2.d2 from t1,t2 
where c1=d1 \G;
obclient [sjzt]> explain select /*+USE_MERGE(t1,t2)*/ t1.c2+t2.d2 from t1,t2 
    -> where c1=d1 \G;
*************************** 1. row ***************************
Query Plan: =================================================
|ID|OPERATOR            |NAME    |EST. ROWS|COST|
-------------------------------------------------
|0 |MERGE JOIN          |        |1        |5   |
|1 | PX COORDINATOR     |        |1        |3   |
|2 |  EXCHANGE OUT DISTR|:EX10000|1        |3   |
|3 |   SORT             |        |1        |2   |
|4 |    TABLE SCAN      |t1      |1        |2   |
|5 | TABLE SCAN         |t2      |1        |2   |
=================================================

Outputs & filters: 
-------------------------------------
  0 - output([t1.c2 + t2.d2]), filter(nil), rowset=256, 
      equal_conds([t1.c1 = t2.d1]), other_conds(nil)
  1 - output([t1.c1], [t1.c2]), filter(nil), rowset=256
  2 - output([t1.c1], [t1.c2]), filter(nil), rowset=256, is_single, dop=1
  3 - output([t1.c1], [t1.c2]), filter(nil), rowset=256, sort_keys([t1.c1, ASC])
  4 - output([t1.c1], [t1.c2]), filter(nil), rowset=256, 
      access([t1.c1], [t1.c2]), partitions(p0)
  5 - output([t2.d1], [t2.d2]), filter(nil), rowset=256, 
      access([t2.d1], [t2.d2]), partitions(p0)

1 row in set (0.002 sec)
上述示例中,执行计划展示的 outputs & filters 中详细展示了 MERGE JOIN 算子的具体输出信息如下:

信息名称	含义
output	该算子输出的表达式。
filter	该算子上的过滤条件。 由于 MJ 算子没有设置 filter,所以为 nil。
equal_conds	归并联接时使用的等值联接条件,左右子节点的结果集相对于联接列必须是有序的。
other_conds	其他联接条件。 例如 Q4 查询中的 t1.c1 + t2.d1 > 10 。


HASH JOIN (HJ)
如下示例中,Q6 查询使用 USE_HASH 的 Hint 指定了查询使用 HJ。
其中,0 号算子是一个 HJ 算子,它有两个子节点,分别是 1 和 4 号算子。该算子的执行逻辑步骤如下:
读取左子节点的数据,根据联接列计算哈希值(例如 t1.c1),构建一张哈希表。
读取右子节点的数据,根据联接列计算哈希值(例如 t2.d1),尝试与对应哈希表中 t1 的数据进行联接。
explain select /*USE_HASH(t1,t2)*/ t1.c2+t2.d2 from t1,t2 
where c1=d1 and c2+d2>1 \G;
obclient [sjzt]> explain select /*USE_HASH(t1,t2)*/ t1.c2+t2.d2 from t1,t2 
    -> where c1=d1 and c2+d2>1 \G;
*************************** 1. row ***************************
Query Plan: =================================================
|ID|OPERATOR            |NAME    |EST. ROWS|COST|
-------------------------------------------------
|0 |HASH JOIN           |        |1        |5   |
|1 | PX COORDINATOR     |        |1        |3   |
|2 |  EXCHANGE OUT DISTR|:EX10000|1        |3   |
|3 |   TABLE SCAN       |t1      |1        |2   | #先根据t1.c1计算hash值,构建hash表
|4 | TABLE SCAN         |t2      |1        |2   | #再根据t2.d1计算hash值,与t1中hash表进行连接。
=================================================

Outputs & filters: 
-------------------------------------
  0 - output([t1.c2 + t2.d2]), filter(nil), rowset=256, 
      equal_conds([t1.c1 = t2.d1]), other_conds([t1.c2 + t2.d2 > 1]) #等值条件和其他条件。
  1 - output([t1.c1], [t1.c2]), filter(nil), rowset=256  #nil ,0表示没有过滤条件
  2 - output([t1.c1], [t1.c2]), filter(nil), rowset=256, is_single, dop=1
  3 - output([t1.c1], [t1.c2]), filter(nil), rowset=256, 
      access([t1.c1], [t1.c2]), partitions(p0)      #非分区表,分区选择的是0号分区。即只有一个分区,就是0号分区。
  4 - output([t2.d1], [t2.d2]), filter(nil), rowset=256, 
      access([t2.d1], [t2.d2]), partitions(p0)      #t2表也是全表访问。

1 row in set (0.003 sec)

上述示例中,执行计划展示中的 outputs & filters 详细展示了 HASH JOIN 算子的输出信息如下:

信息名称	含义
output	该算子输出的表达式。
filter	该算子上的过滤条件。 由于 HJ 算子没有设置 filter,所以为 nil。
equal_conds	等值联接,左右两侧的联接列会用于计算哈希值。
other_conds	其他联接条件。 例如 Q6 查询中的 t1.c2 + t2.d2 > 1。

4.GROUP BY 算子

GROUP BY 算子主要用于在 SQL 中进行分组聚合计算操作。
用于对数据进行分组的算法有 HASH 算法和 MERGE 算法,
因此根据算法可以将 GROUP BY 算子分为两种:HASH GROUP BY 和 MERGE GROUP BY。
执行计划生成时根据 SQL 优化器对于两种算子的代价评估,来选择使用哪种 GROUP BY 算子。
对于普通的聚合函数(SUM/MAX/MIN/AVG/COUNT/STDDEV )也是通过分配 GROUP BY 算子来完成,
而对于只有聚合函数而不含有 GROUP BY 的 SQL,分配的是 SCALAR GROUP BY 算子,
因此 GROUP BY 算子又可以分为三种:SCALAR GROUP BY、HASH GROUP BY 和 MERGE GROUP BY。

######SCALAR GROUP BY:标量分区。
示例 1:含 SCALAR GROUP BY 算子的执行计划
drop table t1; 
create table t1(c1 int,c2 int);
insert into t1 values(1,1);
insert into t1 values(2,2);
insert into t1 values(3,3);
explain select sum(c1) from t1\G;
obclient [sjzt]> explain select sum(c1) from t1\G;
*************************** 1. row ***************************
Query Plan: =============================================
|ID|OPERATOR            |NAME|EST. ROWS|COST|
---------------------------------------------
|0 |EXCHANGE IN REMOTE  |    |1        |3   |
|1 | EXCHANGE OUT REMOTE|    |1        |3   |
|2 |  SCALAR GROUP BY   |    |1        |2   | #标量分区。
|3 |   TABLE SCAN       |t1  |3        |2   |
=============================================

Outputs & filters: 
-------------------------------------
  0 - output([T_FUN_SUM(t1.c1)]), filter(nil)
  1 - output([T_FUN_SUM(t1.c1)]), filter(nil)
  2 - output([T_FUN_SUM(t1.c1)]), filter(nil), rowset=256, 
      group(nil), agg_func([T_FUN_SUM(t1.c1)])   #这里使用的聚合函数是 T_FUN_SUM; nil:没有要分组的列。
  3 - output([t1.c1]), filter(nil), rowset=256,  #nil无过滤条件
      access([t1.c1]), partitions(p0)   #p0全表访问。
1 row in set (0.003 sec)
上述示例中,Q1 查询的执行计划展示中的 outputs & filters 中详细列出了 SCALAR GROUP BY 算子的输出信息如下:
信息名称	含义
output:该算子输出的表达式。
filter:该算子上的过滤条件。 由于示例中 SCALAR GROUP BY 算子未设置 filter,所以为 nil。
group:需要进行分组的列。 例如,Q1 查询中是 SCALAR GROUP BY 算子,所以为 nil。
agg_func:所涉及的聚合函数。 例如,Q1 查询是计算表 t1 的 c1 列数据之和,因此为 T_FUN_SUM(t1.c1)



#######HASH GROUP BY:
示例 2:含 HASH GROUP BY 算子的执行计划
explain select sum(c2) from t1 group by c1 having sum(c2)>2 \G;
obclient [sjzt]> explain select sum(c2) from t1 group by c1 having sum(c2)>2 \G;
*************************** 1. row ***************************
Query Plan: =============================================
|ID|OPERATOR            |NAME|EST. ROWS|COST|
---------------------------------------------
|0 |EXCHANGE IN REMOTE  |    |1        |3   |
|1 | EXCHANGE OUT REMOTE|    |1        |3   |
|2 |  HASH GROUP BY     |    |1        |3   |
|3 |   TABLE SCAN       |t1  |3        |2   |
=============================================

Outputs & filters: 
-------------------------------------
  0 - output([T_FUN_SUM(t1.c2)]), filter(nil)
  1 - output([T_FUN_SUM(t1.c2)]), filter(nil)
  2 - output([T_FUN_SUM(t1.c2)]), filter([T_FUN_SUM(t1.c2) > cast(2, DECIMAL(1, 0))]), rowset=256, #算子过滤条件:T_FUN_SUM(t1.c2) >2;
      group([t1.c1]), agg_func([T_FUN_SUM(t1.c2)]) #聚合函数是T_FUN_SUM 
  3 - output([t1.c2], [t1.c1]), filter(nil), rowset=256, 
      access([t1.c2], [t1.c1]), partitions(p0) #全表访问T1表。
1 row in set (0.007 sec)
上述示例中,Q2 查询的执行计划展示中的 outputs & filters 详细列出了 HASH GROUP BY 算子的输出信息如下:

信息名称	含义
output	该算子输出的表达式。
filter	该算子上的过滤条件。 由于设置要求分组后的 c2 列求和大于 2,因此为 T_FUN_SUM(t1.c2) > 2。
group	需要进行分组的列。 例如,Q2 查询是 HASH GROUP BY 算子,所以为 nil。
agg_func	所涉及的聚合函数。 例如,Q2 查询中计算表 t1 的 c1 列之和,因此为 T_FUN_SUM(t1.c1)。
说明
HASH GROUP BY 算子将会保证在执行时采用 HASH 算法进行分组。

########MERGE GROUP BY
示例 3:含 MERGE GROUP BY 算子的执行计划
explain select /*+NO_USE_HASH_AGGREGATION*/ SUM(c2) from t1 
group by c1 having sum(c2) >2 \G;
obclient [sjzt]> explain select /*+NO_USE_HASH_AGGREGATION*/ SUM(c2) from t1 
    -> group by c1 having sum(c2) >2 \G;
*************************** 1. row ***************************
Query Plan: =======================================
|ID|OPERATOR      |NAME|EST. ROWS|COST|
---------------------------------------
|0 |MERGE GROUP BY|    |1        |3   |
|1 | SORT         |    |3        |3   |  #通过sort算子对table scan算子的结果排序
|2 |  TABLE SCAN  |t1  |3        |2   |
=======================================

Outputs & filters: 
-------------------------------------
  0 - output([T_FUN_SUM(t1.c2)]), filter([T_FUN_SUM(t1.c2) > cast(2, DECIMAL(1, 0))]), rowset=256, 
      group([t1.c1]), agg_func([T_FUN_SUM(t1.c2)])
  1 - output([t1.c1], [t1.c2]), filter(nil), rowset=256, sort_keys([t1.c1, ASC])
  2 - output([t1.c2], [t1.c1]), filter(nil), rowset=256, 
      access([t1.c2], [t1.c1]), partitions(p0)

1 row in set (0.014 sec)
上述示例中,Q3 查询的执行计划展示中的 outputs & filters 中详细列出了 
MERGE GROUP BY 算子的信息,可以看出相同的 SQL 生成执行计划时选择了 
MERGE GROUP BY 算子,其算子基本信息都是相同的,最大的区别是在执行的时候
选择的分组算法不一样。同时,这里的 2 号算子 TABLE SCAN 返回的结果是一个
无序结果,而 GROUP BY 算法采用的是 MERGE GROUP BY,因此必须分配一个 SORT 算子。


#注意
NO_USE_HASH_AGGREGATION 和 USE_HASH_AGGREGATION 的 Hint 可以用于控制 
GROUP BY 算子选择何种算法进行分组。

5.WINDOW FUNCTION

WINDOW FUNCTION 算子用于实现 SQL 中的分析函数(也叫窗口函数),计算窗口内的相关行的结果。

窗口函数与取合函数不同的是,取合函数一组只能返回一行,而窗口函数每组可以返回多行,
组内每一行都是基于窗口的逻辑计算的结果。因此,在执行含有 WINDOW FUNCTION 的 SQL 时 (格式一般为 OVER(...)),
都会在生成执行计划的时候分配一个 WINDOW FUNCTION 算子。

示例:含 WINDOW FUNCTION 算子的执行计划
create table t1(c1 int,c2 int);
explain select max(c1) over(partition by c1 order by c2) from t1\G;
obclient [sjzt]> explain select max(c1) over(partition by c1 order by c2) from t1\G;
*************************** 1. row ***************************
Query Plan: ========================================
|ID|OPERATOR       |NAME|EST. ROWS|COST|
----------------------------------------
|0 |WINDOW FUNCTION|    |3        |5   |  #使用窗口函数时使用该算子。
|1 | SORT          |    |3        |3   |
|2 |  TABLE SCAN   |t1  |3        |2   |
========================================

Outputs & filters: 
-------------------------------------
  0 - output([T_FUN_MAX(t1.c1)]), filter(nil), rowset=256, 
      win_expr(T_FUN_MAX(t1.c1)), partition_by([t1.c1]), order_by([t1.c2, ASC]), window_type(RANGE), upper(UNBOUNDED PRECEDING), lower(CURRENT ROW)
  1 - output([t1.c1], [t1.c2]), filter(nil), rowset=256, sort_keys([t1.c1, ASC], [t1.c2, ASC])
  2 - output([t1.c1], [t1.c2]), filter(nil), rowset=256, 
      access([t1.c1], [t1.c2]), partitions(p0)

1 row in set (0.007 sec)
其中,窗口函数中指定了一个 ORDER BY 或 PARTITION BY 的时候,会在下层分配一个 SORT 算子,
将排序结果返回给窗口函数算子使用。

上述示例中,Q1 查询的执行计划展示中的 outputs & filters 详细列出了 WINDOW FUNCTION 算子的输出信息如下:

信息名称	含义
output	该算子输出的表达式。
filter	该算子上的过滤条件。由于示例中 WINDOW FUNCTION 算子没有设置 filter,所以为 nil。
win_expr	在窗口中使用何种聚合函数。
例如,Q1 查询为求 c1 列的最大值,因此为 T_FUN_MAX(t1.c1)。
partition_by	在窗口中按照何种方式分组。
例如,Q1 查询为按照 c1 列分组,因此为 t1.c1。
order_by	在窗口中按照何种方式排序。
例如,Q1 查询为按照 c2 列排序,因此为 t1.c2。
window_type	窗口类型,包括 RANGE 和 ROWS 两种:
RANGE :按照逻辑位置偏移进行计算窗口上下界限,默认使用 RANGE 方式。
ROWS :按照实际物理位置偏移进行计算窗口上下界限。

例如,Q1 查询未设置窗口类型,因此选择了默认方式 RANGE。
upper	设定窗口的上边界:
UNBOUNDED :无边界,选择最大的值(默认)。
CURRENT ROW :从当前行开始,如果出现数字则表示移动的行数。
PRECEDING :向前取边。
FOLLOWING:向后取边界。

例如,Q1 查询设置的上边界为 UNBOUNDED 和 PRECEDING 的组合,即向前无边界。
lower	设定窗口的下边界,边界属性设置同 upper。
例如,Q1 查询设置的下边界为当前行。

6.SUBPLAN FILTER

SUBPLAN FILTER 算子用于驱动表达式中的子查询执行。

OceanBase 数据库以 NESTED LOOP 算法执行 SUBPLAN FILTER 算子,执行时左边取一行数据,
然后执行右边的子计划。SUBPLAN FILTER 算子可以驱动相关子查询和非相关子查询计算,并且两种执行方式不同。

驱动非相关子查询计算
示例 1:SUBPLAN FILTER 算子驱动非相关子查询计算
explain select /*+NO_REWRITE*/ c1 from t1 where c2>(select max(c2) from t2)\G;
obclient [sjzt]> explain select /*+NO_REWRITE*/ c1 from t1 where c2>(select max(c2) from t2)\G;
*************************** 1. row ***************************
Query Plan: =================================================
|ID|OPERATOR            |NAME    |EST. ROWS|COST|
-------------------------------------------------
|0 |SUBPLAN FILTER      |        |1        |1204|
|1 | TABLE SCAN         |t1      |3        |2   |
|2 | PX COORDINATOR     |        |1        |3   |
|3 |  EXCHANGE OUT DISTR|:EX10000|1        |3   |
|4 |   SCALAR GROUP BY  |        |1        |2   |
|5 |    SUBPLAN SCAN    |VIEW1   |1        |2   |
|6 |     TABLE SCAN     |t2      |1        |2   |
=================================================

Outputs & filters: 
-------------------------------------
  0 - output([t1.c1]), filter([t1.c2 > subquery(1)]), rowset=256, 
      exec_params_([t1.c2]), onetime_exprs_(nil), init_plan_idxs_(nil)
  1 - output([t1.c2], [t1.c1]), filter(nil), rowset=256, 
      access([t1.c2], [t1.c1]), partitions(p0)
  2 - output([T_FUN_MAX(?)]), filter(nil), rowset=256
  3 - output([T_FUN_MAX(?)]), filter(nil), rowset=256, is_single, dop=1
  4 - output([T_FUN_MAX(?)]), filter(nil), rowset=256, 
      group(nil), agg_func([T_FUN_MAX(?)])
  5 - output(nil), filter(nil), rowset=256, 
      access(nil)
  6 - output([1]), filter(nil), rowset=256, 
      access(nil), partitions(p0), 
      limit(1), offset(nil)

1 row in set (0.011 sec)
上述示例中,执行计划展示中 0 号算子 SUBPLAN FILTER 驱动右边 SCALAR GROUP BY 子计划执行,
outputs & filters 详细列出了 SUBPLAN FILTER 算子的输出信息如下:

信息名称	含义
output	该算子输出的列。
filter	该算子上的过滤条件。由于示例中的 SUBPLAN FILTER 算子没有设置 filter,所以为 nil。
exec_params_	右子计划依赖左子计划的参数,执行期由SUBPLAN FILTER 从左子计划中获取,传递给右子计划执行。
由于示例中 SUBPLAN FILTER 算子驱动非相关子查询没有涉及该参数,所以为 nil。
onetime_exprs_	计划中只计算一次的表达式,如果右子计划是非相关子查询,每次重复执行的结果都是一样的,
	所以执行一次后保存在参数集合中。每次执行 SUBPLAN FILTER 时,可以直接从参数集获取右子计划的执行结果。
	参数 subquery(1) 表示 SUBPLAN FILTER 右边第一个子计划是 onetime expr_。
init_plan_ids_	该算子中只需要执行一次的子计划。它与 onetime_exprs_ 的区别是,init_plan_ 返回多行多列,
	onetime_expr_ 返回单行单列。由于示例中的 SQL 查询未设置此项,所以为 nil。
SUBPLAN FILTER 算子驱动非相关子查询计算的一般执行流程如下:

SUBPLAN FILTER 在启动时会执行 onetime_exprs_。
从参数中拿到右边非相关子查询的结果,下推 filter 到左边计划,执行左边的查询。
输出左边查询的行。
驱动相关子查询计算


示例 2:SUBPLAN FILTER 算子驱动相关子查询计算
explain select /*+NO_REWRITE*/c1 from t1 where c2>
(select max(c2) from t2 where t1.c1=t2.d1)\G;

obclient [sjzt]> explain select /*+NO_REWRITE*/c1 from t1 where c2>
    -> (select max(c2) from t2 where t1.c1=t2.d1)\G;
*************************** 1. row ***************************
Query Plan: ==================================================
|ID|OPERATOR                |NAME |EST. ROWS|COST|
--------------------------------------------------
|0 |SUBPLAN FILTER          |     |1        |56  |
|1 | TABLE SCAN             |t1   |3        |2   |
|2 | SCALAR GROUP BY        |     |1        |18  |
|3 |  SUBPLAN SCAN          |VIEW1|1        |18  |
|4 |   DISTRIBUTED TABLE GET|t2   |1        |18  |
==================================================

Outputs & filters: 
-------------------------------------
  0 - output([t1.c1]), filter([t1.c2 > subquery(1)]), rowset=256, 
      exec_params_([t1.c2], [t1.c1]), onetime_exprs_(nil), init_plan_idxs_(nil)#返回一行和返回多行子计划都没有
  1 - output([t1.c1], [t1.c2]), filter(nil), rowset=256, 
      access([t1.c1], [t1.c2]), partitions(p0)
  2 - output([T_FUN_MAX(?)]), filter(nil), rowset=256, 
      group(nil), agg_func([T_FUN_MAX(?)])
  3 - output(nil), filter(nil), rowset=256, 
      access(nil)
  4 - output([1]), filter(nil), rowset=256, 
      access(nil), partitions(p0), 
      limit(1), offset(nil)

1 row in set (0.004 sec)
上述示例中,执行计划展示中 0 号算子 SUBPLAN FILTER 驱动右边 SCALAR GROUP BY 子计划执行,
outputs & filters 详细列出了 SUBPLAN FILTER 算子的输出信息如下:
信息名称	含义
output	该算子输出的列。
filter	该算子上的过滤条件。 例如,示例 2 中的 SQL 查询过滤条件为 t1.c2 > subquery(1)。
exec_params_	右子计划依赖左子计划的参数,执行期由 SUBPLAN FILTER 从左子计划中获取,传递给右子计划执行。
	左边输出一行数据后需要下推的参数,在非相关子查询中一般没有下推的参数。
onetime_exprs_	计划中只计算一次的表达式,如果右子计划是非相关子查询,每次重复执行的结果都是一样的,
	所以执行一次后保存在参数集合中。每次执行 SUBPLAN FILTER 时,可以直接从参数集获取右子计划的执行结果。
	参数 subquery(1) 表示 SUBPLAN FILTER 右边第一个子计划是 onetime expr_。由于示例中的 SQL 查询未设置此项,所以为 nil。
init_plan_idxs_	该算子中只需要执行一次的子计划。 与 onetime_exprs_ 的区别是,init_plan_ 返回多行多列,
	onetime_expr_ 返回单行单列。由于示例中的 SQL 查询未设置此项,所以为 nil。
SUBPLAN FILTER 算子驱动相关子查询计算的一般执行流程如下:

SUBPLAN FILTER 在启动时会执行 onetime_exprs_。
执行左边的查询,输出一行后,计算相关参数,下推到右边,执行右边的子查询。
执行 filter,输出符合条件的数据行。

7.DISTINCT

DISTINCT 算子用于为对数据行去重,包括去除重复的 NULL 值。
DISTINCT 算子包括 HASH DISTINCT 和 MERGE DISTINCT。
HASH DISTINCT
HASH DISTINCT 算子使用 Hash 算法执行 DISTINCT 运算。

示例 1:使用 Hash 算法执行 DISTINCT 运算,对 t1 表的 c1 列进行去重处理

drop table t1; 
drop table t2; 
CREATE TABLE t1(c1 INT, c2 INT);
CREATE TABLE t2(c1 INT, c2 INT);
explain select /*+USE_HASH_AGGREGATION*/ DISTINCT c1 from t1 \G;
obclient [sjzt]> explain select /*+USE_HASH_AGGREGATION*/ DISTINCT c1 from t1 \G;
*************************** 1. row ***************************
Query Plan: =============================================
|ID|OPERATOR            |NAME|EST. ROWS|COST|
---------------------------------------------
|0 |EXCHANGE IN REMOTE  |    |1        |3   |
|1 | EXCHANGE OUT REMOTE|    |1        |3   |
|2 |  HASH DISTINCT     |    |1        |2   |
|3 |   TABLE SCAN       |t1  |1        |2   |
=============================================

Outputs & filters: 
-------------------------------------
  0 - output([t1.c1]), filter(nil)
  1 - output([t1.c1]), filter(nil)
  2 - output([t1.c1]), filter(nil), rowset=256, 
      distinct([t1.c1])
  3 - output([t1.c1]), filter(nil), rowset=256, 
      access([t1.c1]), partitions(p0)

1 row in set (0.037 sec)
上述示例中,执行计划展示中 0 号算子 HASH DISTINCT 执行去重运算,
outputs & filters 详细展示了 HASH DISTINCT 算子的具体输出信息如下:

信息名称	含义
output	该算子的输出列。
filter	该算子的过滤谓词。 由于示例中 HASH DISTINCT 算子没有设置 filter,所以为 nil。
partition	查询需要扫描的分区。
distinct	指定需要去重的列。 例如,distinct([t1.c1]) 的参数 t1.c1 指定对 t1 表的 c1 列进行去重处理,并且采用 Hash 算法

MERGE DISTINCT
MERGE DISTINCT 算子使用 Merge 算法执行 DISTINCT 运算。

示例 2:使用 Merge 算法执行 DISTINCT 运算
explain select /*+NO_USE_HASH_AGGREGATION*/ DISTINCT c1 from t1\G;
obclient [sjzt]> explain select /*+NO_USE_HASH_AGGREGATION*/ DISTINCT c1 from t1\G;
*************************** 1. row ***************************
Query Plan: ======================================
|ID|OPERATOR     |NAME|EST. ROWS|COST|
--------------------------------------
|0 |HASH DISTINCT|    |6        |4   |   #虽然指定了NO_USE_HASH_AGGREGATION,但是还是使用了HASH算法。
|1 | TABLE SCAN  |t1  |6        |2   |   #可能是BUG; 
======================================

Outputs & filters: 
-------------------------------------
  0 - output([t1.c1]), filter(nil), rowset=256, 
      distinct([t1.c1])
  1 - output([t1.c1]), filter(nil), rowset=256, 
      access([t1.c1]), partitions(p0)

1 row in set (0.003 sec)


上述示例中,0 号算子 MERGE DISTINCT 执行去重运算,采用了 Merge 算法,
并且由于 2 号算子输出的数据是无序的,而 MERGE DISTINCT 算子需要输入的数据有序,
所以在执行去重运算前需要使用 SORT 算子对数据排序。
执行计划展示中的 outputs & filters 详细展示了 MERGE DISTINCT 算子的输出信息如下:

信息名称	含义
output	该算子的输出列。
filter	该算子的过滤谓词。 由于示例中 MERGE DISTINCT 算子没有设置 filter,所以为 nil。
distinct	指定需要去重的列。 例如,distinct([t1.c1]) 的参数 t1.c1 指定对 t1 表的 c1 列进行去重处理,
并且采用 Merge 算法


##########################MATERIAL###################################
MATERIAL 算子用于物化下层算子输出的数据。

OceanBase 数据库以流式数据执行计划,但有时算子需要等待下层算子输出所有数据后才能够开始执行,
所以需要在下方添加一个 MATERIAL 算子物化所有的数据。或者在子计划需要重复执行的时候,
使用 MATERIAL 算子可以避免重复执行。

如下示例中,t1 表与 t2 表执行 NESTED LOOP JOIN 运算时,右表需要重复扫描,
可以在右表有一个 MATERIAL 算子,保存 t2 表的所有数据。
drop table t1; 
drop table t2; 
create table t1(c1 int,c2 int,c3 int);
create table t2(c1 int,c2 int,c3 int);
explain select /*+ORDERED USE_NL(t2)*/* from t1,t2 where t1.c1=t2.c1\G;
obclient [sjzt]> explain select /*+ORDERED USE_NL(t2)*/* from t1,t2 where t1.c1=t2.c1\G;
*************************** 1. row ***************************
Query Plan: =================================================
|ID|OPERATOR            |NAME    |EST. ROWS|COST|
-------------------------------------------------
|0 |NESTED-LOOP JOIN    |        |1        |6   |
|1 | PX COORDINATOR     |        |1        |4   |
|2 |  EXCHANGE OUT DISTR|:EX10000|1        |3   |
|3 |   TABLE SCAN       |t1      |1        |2   |
|4 | MATERIAL           |        |1        |2   | #物化算子保存t2数据避免反复执行。
|5 |  TABLE SCAN        |t2      |1        |2   |
=================================================

Outputs & filters: 
-------------------------------------
  0 - output([t1.c1], [t1.c2], [t1.c3], [t2.c1], [t2.c2], [t2.c3]), filter(nil), rowset=256, 
      conds([t1.c1 = t2.c1]), nl_params_(nil)
  1 - output([t1.c1], [t1.c2], [t1.c3]), filter(nil), rowset=256
  2 - output([t1.c1], [t1.c2], [t1.c3]), filter(nil), rowset=256, is_single, dop=1
  3 - output([t1.c1], [t1.c2], [t1.c3]), filter(nil), rowset=256, 
      access([t1.c1], [t1.c2], [t1.c3]), partitions(p0)
  4 - output([t2.c1], [t2.c2], [t2.c3]), filter(nil), rowset=256
  5 - output([t2.c1], [t2.c2], [t2.c3]), filter(nil), rowset=256, 
      access([t2.c1], [t2.c2], [t2.c3]), partitions(p0)

1 row in set (0.009 sec)
上述示例中,执行计划展示中 2 号算子 MATERIAL 的功能是保存 t2 表的数据,以避免每次联接都从磁盘扫描 t2 表的数据。
执行计划展示中的 outputs & filters 详细展示了 MATERIAL 算子的输出信息如下:

信息名称	含义
output	该算子输出的表达式。 其中 rownum() 表示 ROWNUM 对应的表达式。
filter	该算子上的过滤条件。 由于示例中 MATERIAL 算子没有设置 filter,所以为 nil。

8.SORT

SORT 算子用于对输入的数据进行排序。

示例:对 t1 表的数据排序,并按照 c1 列降序排列和 c2 列升序排列
drop table t1; 
create table t1(c1 int,c2 int);
create index i1 on t1(c1);
explain select c1 from t1 order by c1 desc,c2 asc\G;
obclient [sjzt]> explain select c1 from t1 order by c1 desc,c2 asc\G;
*************************** 1. row ***************************
Query Plan: =============================================
|ID|OPERATOR            |NAME|EST. ROWS|COST|
---------------------------------------------
|0 |EXCHANGE IN REMOTE  |    |1        |3   |
|1 | EXCHANGE OUT REMOTE|    |1        |3   |
|2 |  SORT              |    |1        |2   |
|3 |   TABLE SCAN       |t1  |1        |2   |
=============================================

Outputs & filters: 
-------------------------------------
  0 - output([t1.c1]), filter(nil)
  1 - output([t1.c1]), filter(nil)
  2 - output([t1.c1]), filter(nil), rowset=256, sort_keys([t1.c1, DESC], [t1.c2, ASC])
  3 - output([t1.c1], [t1.c2]), filter(nil), rowset=256, 
      access([t1.c1], [t1.c2]), partitions(p0)

1 row in set (0.009 sec)

上述示例中,执行计划展示中 0 号算子 SORT 对 t1 表的数据进行排序,
执行计划展示中的 outputs & filters 详细展示了 SORT 算子的输出信息如下:

信息名称	含义
output	该算子的输出列。
filter	该算子的过滤谓词。 由于示例中 SORT 算子没有设置 filter,所以为 nil。
sort_keys([column, DESC],[column, ASC] ...)	按 column 列排序。
DESC:降序排列。
ASC:升序排列。
例如,sort_keys([t1.c1, DESC],[t1.c2, ASC]) 中指定排序键分别为 c1 列和 c2 列,并且以 c1 列降序, c2 列升序排列。
prefix_pos(n)	排序列的有序位置。 例如,sort_keys([t1.c1, DESC], [t1.c2, ASC]),
prefix_pos(1) 表示 t1 表的数据在 c1 列上有序,仅需要对 c2 列排序。
如果 ORDER BY 语句块后面还有 LIMIT(仅适用于 MySQL 模式)语句,优化器会进一步优化执行计划,
生成 TOP-N SORT算子,即采用堆排序来选择 TOP-N 的数据,示例如下:
explain select * from t1 where c1>1 order by c1,c2 limit 10\G;
obclient [sjzt]> explain select *from t1 where c1>1 order by c1,c2 limit 10\G;
*************************** 1. row ***************************
Query Plan: ===============================================
|ID|OPERATOR            |NAME  |EST. ROWS|COST|
-----------------------------------------------
|0 |EXCHANGE IN REMOTE  |      |1        |6   |
|1 | EXCHANGE OUT REMOTE|      |1        |5   |
|2 |  TOP-N SORT        |      |1        |5   |
|3 |   TABLE SCAN       |t1(i1)|1        |5   |
===============================================

Outputs & filters: 
-------------------------------------
  0 - output([t1.c1], [t1.c2]), filter(nil)
  1 - output([t1.c1], [t1.c2]), filter(nil)
  2 - output([t1.c1], [t1.c2]), filter(nil), rowset=256, sort_keys([t1.c1, ASC], [t1.c2, ASC]), topn(10), prefix_pos(1)
  3 - output([t1.c1], [t1.c2]), filter(nil), rowset=256, 
      access([t1.__pk_increment], [t1.c1], [t1.c2]), partitions(p0)

1 row in set (0.007 sec)
上述示例的执行计划展示中,topn(10) 表示 LIMIT 10 需要的数据量

9.LIMIT

create table t1(c1 int,c2 int);
create table t2(c1 int,c2 int);
insert into t1 values(1,1);
insert into t1 values(2,2);
insert into t1 values(3,3);
insert into t2 values(1,1);
insert into t2 values(2,2);
insert into t2 values(3,3);
explain select t1.c1 from t1,t2 limit 1 offset 1\G;
obclient [sjzt]> explain select t1.c1 from t1,t2 limit 1 offset 1\G;
*************************** 1. row ***************************
Query Plan: ========================================================
|ID|OPERATOR                   |NAME    |EST. ROWS|COST|
--------------------------------------------------------
|0 |LIMIT                      |        |0        |4   |
|1 | NESTED-LOOP JOIN CARTESIAN|        |1        |4   |
|2 |  SUBPLAN SCAN             |VIEW1   |1        |2   |
|3 |   TABLE SCAN              |t1      |1        |2   |
|4 |  MATERIAL                 |        |1        |2   | #t2表的数据通过物化方式保存
|5 |   PX COORDINATOR          |        |1        |2   |
|6 |    EXCHANGE OUT DISTR     |:EX10000|1        |2   |
|7 |     SUBPLAN SCAN          |VIEW2   |1        |2   |
|8 |      TABLE SCAN           |t2      |1        |2   |
========================================================

Outputs & filters: 
-------------------------------------
  0 - output([VIEW1.t1.c1]), filter(nil), rowset=256, limit(1), offset(1)
  1 - output([VIEW1.t1.c1]), filter(nil), rowset=256, 
      conds(nil), nl_params_(nil)
  2 - output([VIEW1.t1.c1]), filter(nil), rowset=256, 
      access([VIEW1.t1.c1])
  3 - output([t1.c1]), filter(nil), rowset=256, 
      access([t1.c1]), partitions(p0), 
      limit(1 + 1), offset(nil)
  4 - output(nil), filter(nil), rowset=256
  5 - output(nil), filter(nil), rowset=256
  6 - output(nil), filter(nil), rowset=256, is_single, dop=1
  7 - output(nil), filter(nil), rowset=256, 
      access(nil)
  8 - output([1]), filter(nil), rowset=256, 
      access(nil), partitions(p0), 
      limit(1 + 1), offset(nil)

1 row in set (0.013 sec)

explain select * from t1 limit 2\G;
obclient [sjzt]> explain select * from t1 limit 2\G;
*************************** 1. row ***************************
Query Plan: ===================================
|ID|OPERATOR  |NAME|EST. ROWS|COST|
-----------------------------------
|0 |TABLE SCAN|t1  |1        |2   |
===================================

Outputs & filters: 
-------------------------------------
  0 - output([t1.c1], [t1.c2]), filter(nil), rowset=256, 
      access([t1.c1], [t1.c2]), partitions(p0), 
      limit(2), offset(nil)

1 row in set (0.003 sec)
上述示例中,Q1 查询的执行计划展示中的 outputs & filters 详细列出了 LIMIT 算子的输出信息如下:

信息名称	含义
output	该算子输出的表达式。
filter	该算子上的过滤条件。 由于示例中 LIMIT 算子没有设置 filter,所以为 nil。
limit	限制输出的行数,是一个常量。
offset	距离当前位置的偏移行数,是一个常量。 由于示例中的 SQL 中不含有 offset,因此生成的计划中为 nil。
Q2 查询的执行计划展示中,虽然 SQL 中含有 LIMIT,但是并未分配 LIMIT 算子,
而是将相关表达式下压到了 TABLE SCAN 算子上,这种下压 LIMIT 行为是 SQL 优化器的一种优化方式,
详细信息请参见 TABLE SCAN。

10.FOR UPDATE

FOR UPDATE 算子用于对表中的数据进行加锁操作。
OceanBase 数据库支持的 FOR UPDATE 算子包括 FOR UPDATE 和 MULTI FOR UPDATE。
FOR UPDATE 算子执行查询的一般流程如下:
首先执行 SELECT 语句部分,获得查询结果集。
对查询结果集相关的记录进行加锁操作。
FOR UPDATE
FOR UPDATE 用于对单表(或者单个分区)进行加锁。
如下示例中,Q1 查询是对 t1 表中满足 c1 = 1 的行进行加锁。t1 表是一张单分区的表,所以 1 号算子生成了一个 FOR UPDATE 算子。

drop table t1;
drop table t2; 
create table t1(c1 int,c2 int);
create table t2(c1 int,c2 int);
insert into t1 values(1,1);
insert into t1 values(2,2);
insert into t1 values(3,3);
insert into t2 values(1,1);
insert into t2 values(2,2);
insert into t2 values(3,3);
explain select * from t1 where c1=1 for update\G;
obclient [sjzt]> explain select * from t1 where c1=1 for update\G;
*************************** 1. row ***************************
Query Plan: =============================================
|ID|OPERATOR            |NAME|EST. ROWS|COST|
---------------------------------------------
|0 |EXCHANGE IN REMOTE  |    |1        |3   |
|1 | EXCHANGE OUT REMOTE|    |1        |3   |
|2 |  MATERIAL          |    |1        |2   |
|3 |   FOR UPDATE       |    |1        |2   |
|4 |    TABLE SCAN      |t1  |1        |2   |
=============================================

Outputs & filters: 
-------------------------------------
  0 - output([t1.c1], [t1.c2]), filter(nil)
  1 - output([t1.c1], [t1.c2]), filter(nil)
  2 - output([t1.c1], [t1.c2]), filter(nil), rowset=256
  3 - output([t1.c1], [t1.c2]), filter(nil), rowset=256, lock tables(t1)
  4 - output([t1.__pk_increment], [t1.c1], [t1.c2]), filter([t1.c1 = 1]), rowset=256, 
      access([t1.__pk_increment], [t1.c1], [t1.c2]), partitions(p0)

1 row in set (0.006 sec)

上述示例中,Q1 查询的执行计划展示中的 outputs & filters 详细列出了 FOR UPDATE 算子的输出信息如下:

信息名称	含义
output	该算子输出的表达式。
filter	该算子上的过滤条件。 由于示例中 FOR UPDATE 算子没有设置 filter,所以为 nil。
lock tables	需要加锁的表。
MULTI FOR UPDATE
MULTI FOR UPDATE 用于对多表(或者多个分区)进行加锁操作。

如下示例中,Q2 查询是对 t1 和 t2 两张表的数据进行加锁,加锁对象是满足 c1 = 1 AND c1 = d1 的行。
由于需要对多个表的行进行加锁,因此 1 号算子是 MULTI FOR UPDATE;
drop table t1; 
drop table t2; 
create table t1(c1 int,c2 int);
create table t2(d1 int,d2 int);
explain select * from t1 ,t2 where c1=1 and c1=d1 for update \G; 
obclient [sjzt]> explain select * from t1 ,t2 where c1=1 and c1=d1 for update \G; 
*************************** 1. row ***************************
Query Plan: =========================================================
|ID|OPERATOR                    |NAME    |EST. ROWS|COST|
---------------------------------------------------------
|0 |MATERIAL                    |        |1        |5   |
|1 | DISTRIBUTED FOR UPDATE     |        |1        |5   |
|2 |  NESTED-LOOP JOIN CARTESIAN|        |1        |5   |
|3 |   PX COORDINATOR           |        |1        |3   |
|4 |    EXCHANGE OUT DISTR      |:EX10000|1        |3   |
|5 |     TABLE SCAN             |t1      |1        |2   |
|6 |   MATERIAL                 |        |1        |2   |
|7 |    TABLE SCAN              |t2      |1        |2   |
=========================================================

Outputs & filters: 
-------------------------------------
  0 - output([t1.c1], [t1.c2], [t2.d1], [t2.d2]), filter(nil), rowset=256
  1 - output([t1.c1], [t1.c2], [t2.d1], [t2.d2]), filter(nil), rowset=256, lock tables(t1, t2)
  2 - output([t1.c1], [t1.c2], [t2.d1], [t2.d2], [t1.__pk_increment], [t2.__pk_increment]), filter(nil), rowset=256, 
      conds(nil), nl_params_(nil)
  3 - output([t1.c1], [t1.c2], [t1.__pk_increment]), filter(nil), rowset=256
  4 - output([t1.c1], [t1.c2], [t1.__pk_increment]), filter(nil), rowset=256, is_single, dop=1
  5 - output([t1.__pk_increment], [t1.c1], [t1.c2]), filter([t1.c1 = 1]), rowset=256, 
      access([t1.__pk_increment], [t1.c1], [t1.c2]), partitions(p0)
  6 - output([t2.d1], [t2.d2], [t2.__pk_increment]), filter(nil), rowset=256
  7 - output([t2.__pk_increment], [t2.d1], [t2.d2]), filter([1 = t2.d1]), rowset=256, 
      access([t2.__pk_increment], [t2.d1], [t2.d2]), partitions(p0)

1 row in set (0.081 sec)
上述示例中,Q2 查询的执行计划展示中的 outputs & filters 详细列出了 MULTI FOR UPDATE 算子的信息如下:

信息名称	含义
output	该算子输出的列。
filter	该算子上的过滤条件。 由于示例中 MULTI FOR UPDATE 算子没有设置 filter,所以为 nil。
lock tables	需要加锁的表。

11.SELECT INTO

SELECT INTO 算子用于将查询结果赋值给变量列表,查询仅返回一行数据。
如下示例查询中, SELECT 输出列为 COUNT(*) 和 MAX(c1),其查询结果分别赋值给变量 @a 和 @b。

drop table t1; 
create table t1(c1 int,c2 int);
insert into t1 values(1,1);
insert into t1 values(2,2);
explain select count(*) ,max(c1) into @a,@b from t1\G;
obclient [sjzt]> explain select count(*) ,max(c1) into @a,@b from t1\G;
*************************** 1. row ***************************
Query Plan: =================================================
|ID|OPERATOR            |NAME    |EST. ROWS|COST|
-------------------------------------------------
|0 |SELECT INTO         |        |1        |3   |
|1 | PX COORDINATOR     |        |1        |3   |
|2 |  EXCHANGE OUT DISTR|:EX10000|1        |3   |
|3 |   SCALAR GROUP BY  |        |1        |2   |
|4 |    TABLE SCAN      |t1      |2        |2   |
=================================================

Outputs & filters: 
-------------------------------------
  0 - output([T_FUN_COUNT(*)], [T_FUN_MAX(t1.c1)]), filter(nil), rowset=256
  1 - output([T_FUN_COUNT(*)], [T_FUN_MAX(t1.c1)]), filter(nil), rowset=256
  2 - output([T_FUN_COUNT(*)], [T_FUN_MAX(t1.c1)]), filter(nil), rowset=256, is_single, dop=1
  3 - output([T_FUN_COUNT(*)], [T_FUN_MAX(t1.c1)]), filter(nil), rowset=256, 
      group(nil), agg_func([T_FUN_COUNT(*)], [T_FUN_MAX(t1.c1)])
  4 - output([t1.c1]), filter(nil), rowset=256, 
      access([t1.c1]), partitions(p0)

1 row in set (0.005 sec)
上述示例中,执行计划展示中的 outputs & filters 详细列出了 SELECT INTO 算子的输出信息如下:

信息名称	含义
output	该算子赋值给变量列表的表达式。
filter	该算子上的过滤条件。 由于示例中 SELECT INTO 算子没有设置 filter,所以为 nil

12.SUBPLAN SCAN

SUBPLAN SCAN 算子用于展示优化器从哪个视图访问数据。

当查询的 FROM TABLE 为视图时,执行计划中会分配 SUBPLAN SCAN 算子。
SUBPLAN SCAN 算子类似于 TABLE SCAN 算子,但它不从基表读取数据,而是读取孩子节点的输出数据。
如下示例中,Q1 查询中 1 号算子为视图中查询生成,0 号算子 SUBPLAN SCAN 读取 1 号算子并输出

drop table t1; 
create table t1(c1 int,c2 int);
insert into t1 values(1,1);
insert into t1 values(2,2);
create view v as select * from t1 limit 5;
explain select * from v where c1>0\G;
obclient [sjzt]> explain select * from v where c1>0\G;
*************************** 1. row ***************************
Query Plan: =====================================
|ID|OPERATOR    |NAME|EST. ROWS|COST|
-------------------------------------
|0 |SUBPLAN SCAN|v   |1        |2   |
|1 | TABLE SCAN |t1  |2        |2   |
=====================================

Outputs & filters: 
-------------------------------------
  0 - output([v.c1], [v.c2]), filter([v.c1 > 0]), rowset=256, 
      access([v.c1], [v.c2])
  1 - output([t1.c1], [t1.c2]), filter(nil), rowset=256, 
      access([t1.c1], [t1.c2]), partitions(p0), 
      limit(5), offset(nil)

1 row in set (0.034 sec)

目前 LIMIT 算子只支持 MySQL 模式的 SQL 场景。详细信息请参考 LIMIT。
上述示例中,Q1 查询的执行计划展示中的 outputs & filters 详细列出了 SUBPLAN SCAN 算子的输出信息如下:
信息名称	含义
output	该算子输出的表达式。
filter	该算子上的过滤条件。 例如 filter([v.c1 > 0]) 中的 v.c1 > 0。
access	该算子从子节点读取的需要使用的列名。
当 FROM TABLE 为视图并且查询满足一定条件时能够对查询进行视图合并改写,
此时执行计划中并不会出现 SUBPLAN SCAN。如下例所示,Q2 查询相比 Q1 查询减少了过滤条件,
不再需要分配 SUBPLAN SCAN 算子。

explain select * from v\G;
obclient [sjzt]> explain select * from v\G;
*************************** 1. row ***************************
Query Plan: ===================================
|ID|OPERATOR  |NAME|EST. ROWS|COST|
-----------------------------------
|0 |TABLE SCAN|t1  |2        |2   |
===================================

Outputs & filters: 
-------------------------------------
  0 - output([t1.c1], [t1.c2]), filter(nil), rowset=256, 
      access([t1.c1], [t1.c2]), partitions(p0), 
      limit(5), offset(nil)

1 row in set (0.003 sec)

13.UNION

UNION 算子用于将两个查询的结果集进行并集运算。

OceanBase 数据库支持的 UNION 算子包括 UNION ALL、HASH UNION DISTINCT 和 MERGE UNION DISTINCT。

UNION ALL
UNION ALL 用于直接对两个查询结果集进行合并输出。

如下示例中,Q1 对两个查询使用 UNION ALL 进行联接,使用 UNION ALL 算子进行并集运算。算子执行时依次输出左右子节点所有输出结果。

drop table t1; 
create table t1 (c1 int primary key ,c2 int);
insert into t1 values(1,1);
insert into t1 values(2,2);
explain select c1 from t1 union all select c2 from t1\G;
obclient [sjzt]> explain select c1 from t1 union all select c2 from t1\G;
*************************** 1. row ***************************
Query Plan: =============================================
|ID|OPERATOR            |NAME|EST. ROWS|COST|
---------------------------------------------
|0 |EXCHANGE IN REMOTE  |    |4        |6   |
|1 | EXCHANGE OUT REMOTE|    |4        |5   |
|2 |  UNION ALL         |    |4        |4   |
|3 |   TABLE SCAN       |t1  |2        |2   |
|4 |   TABLE SCAN       |t1  |2        |2   |
=============================================

Outputs & filters: 
-------------------------------------
  0 - output([UNION([1])]), filter(nil)
  1 - output([UNION([1])]), filter(nil)
  2 - output([UNION([1])]), filter(nil), rowset=256
  3 - output([t1.c1]), filter(nil), rowset=256, 
      access([t1.c1]), partitions(p0)
  4 - output([t1.c2]), filter(nil), rowset=256, 
      access([t1.c2]), partitions(p0)

1 row in set (0.004 sec)

上述示例中,执行计划展示中的 outputs & filters 详细列出了 UNION ALL 算子的输出信息如下:

信息名称	含义
output	该算子的输出表达式。
filter	该算子上的过滤条件。 由于示例中 UNION ALL 算子没有设置 filter,所以为 nil。
MERGE UNION DISTINCT
MERGE UNION DISTINCT 用于对结果集进行并集、去重后进行输出。
如下示例中,Q2 对两个查询使用 UNION DISTINCT 进行联接, c1 列有可用排序,
0 号算子生成 MERGE UNION DISTINCT 进行取并集、去重。由于 c2 列无可用排序,
所以在 3 号算子上分配了 SORT 算子进行排序。算子执行时从左右子节点读取有序输入,
进行合并得到有序输出并去重。

explain select c1 from t1 union select c2 from t1\G;
obclient [sjzt]> explain select c1 from t1 union select c2 from t1\G;
*************************** 1. row ***************************
Query Plan: ===============================================
|ID|OPERATOR              |NAME|EST. ROWS|COST|
-----------------------------------------------
|0 |EXCHANGE IN REMOTE    |    |4        |6   |
|1 | EXCHANGE OUT REMOTE  |    |4        |6   |
|2 |  MERGE UNION DISTINCT|    |4        |4   |
|3 |   TABLE SCAN         |t1  |2        |2   | #c1有可用需要
|4 |   SORT               |    |2        |2   | #c2无可用需要右边的结果需要排序
|5 |    TABLE SCAN        |t1  |2        |2   |
===============================================

Outputs & filters: 
-------------------------------------
  0 - output([UNION([1])]), filter(nil)
  1 - output([UNION([1])]), filter(nil)
  2 - output([UNION([1])]), filter(nil), rowset=256
  3 - output([t1.c1]), filter(nil), rowset=256, 
      access([t1.c1]), partitions(p0)
  4 - output([t1.c2]), filter(nil), rowset=256, sort_keys([t1.c2, ASC])
  5 - output([t1.c2]), filter(nil), rowset=256, 
      access([t1.c2]), partitions(p0)

1 row in set (0.003 sec)
上述示例的执行计划展示中的 outputs & filters 详细列出了 MERGE UNION DISTINCT 算子的输出信息,
字段的含义与 UNION ALL 算子相同。


HASH UNION DISTINCT
HASH UNION DISTINCT 用于对结果集进行并集、去重后进行输出。
如下示例中,Q3 对两个查询使用 UNION DISTINCT 进行联接,无可利用排序,
0 号算子使用 HASH UNION DISTINCT 进行并集、去重。算子执行时读取左右子节点输出,
建立哈希表进行去重,最终输出去重后结果。
explain select c2 from t1 union select c2 from t1\G;
obclient [sjzt]> explain select c2 from t1 union select c2 from t1\G;
*************************** 1. row ***************************
Query Plan: ============================================
|ID|OPERATOR           |NAME|EST. ROWS|COST|
--------------------------------------------
|0 |HASH UNION DISTINCT|    |4        |5   | #需要去重
|1 | TABLE SCAN        |t1  |2        |2   | #都取c2,都无序。
|2 | TABLE SCAN        |t1  |2        |2   |
============================================

Outputs & filters: 
-------------------------------------
  0 - output([UNION([1])]), filter(nil), rowset=256
  1 - output([t1.c2]), filter(nil), rowset=256, 
      access([t1.c2]), partitions(p0)
  2 - output([t1.c2]), filter(nil), rowset=256, 
      access([t1.c2]), partitions(p0)

1 row in set (0.005 sec)
上述示例的执行计划展示中的 outputs & filters 详细列出了 HASH UNION DISTINCT 算子的输出信息,
字段的含义与 UNION ALL 算子相同

14.INTERSECT

INTERSECT 算子用于对左右子节点算子输出进行交集运算,并进行去重。

OceanBase 数据库支持的 INTERSECT 算子包括 MERGE INTERSECT DISTINCT 和 HASH INTERSECT DISTINCT。

MERGE INTERSECT DISTINCT
如下示例中,Q1 对两个查询使用 INTERSECT 联接,c1 列有可用排序,0 号算子生成了
 MERGE INTERSECT DISTINCT 进行求取交集、去重。由于 c2 列无可用排序,
 所以在 3 号算子上分配了 SORT 算子进行排序。
 算子执行时从左右子节点读取有序输入,利用有序输入进行 MERGE,实现去重并得到交集结果

drop table t1; 
create table t1(c1 int primary key,c2 int);
insert into t1 values(1,1);
insert into t1 values(2,2);
explain select c1 from t1 intersect select c2 from t1\G;
obclient [sjzt]> explain select c1 from t1 intersect select c2 from t1\G;
*************************** 1. row ***************************
Query Plan: =================================================
|ID|OPERATOR                |NAME|EST. ROWS|COST|
-------------------------------------------------
|0 |MERGE INTERSECT DISTINCT|    |2        |4   |
|1 | TABLE SCAN             |t1  |2        |2   | #c1有序所以不需要排序
|2 | SORT                   |    |2        |2   | #c2上没有序列,需要排序
|3 |  TABLE SCAN            |t1  |2        |2   |
=================================================

Outputs & filters: 
-------------------------------------
  0 - output([INTERSECT([1])]), filter(nil), rowset=256
  1 - output([t1.c1]), filter(nil), rowset=256, 
      access([t1.c1]), partitions(p0)
  2 - output([t1.c2]), filter(nil), rowset=256, sort_keys([t1.c2, ASC])
  3 - output([t1.c2]), filter(nil), rowset=256, 
      access([t1.c2]), partitions(p0)

1 row in set (0.005 sec)
上述示例中,执行计划展示中的 outputs & filters 详细列出了所有 INTERSECT 算子的输出信息如下:

信息名称	含义
output	该算子的输出表达式。 使用 INTERSECT 联接的两个子算子对应输出,
即表示交集运算输出结果中的一列,括号内部为左右子节点对应此列的输出列。
filter	该算子上的过滤条件。 由于示例中 INTERSECT 算子没有设置 filter,所以为 nil。
HASH INTERSECT DISTINCT
如下例所示,Q2 对两个查询使用 INTERSECT 进行联接,无可利用的排序,
0 号算子使用 HASH INTERSECT DISTINCT 进行求取交集、去重。算子执行时先读取一侧子节点输出建立哈希表并去重,
再读取另一侧子节点利用哈希表求取交集并去重。


explain select c2 from t1 intersect select c2 from t1\G;
obclient [sjzt]> explain select c2 from t1 intersect select c2 from t1\G;
*************************** 1. row ***************************
Query Plan: ================================================
|ID|OPERATOR               |NAME|EST. ROWS|COST|
------------------------------------------------
|0 |HASH INTERSECT DISTINCT|    |2        |5   |
|1 | TABLE SCAN            |t1  |2        |2   | #c2上都没有序列。
|2 | TABLE SCAN            |t1  |2        |2   |
================================================

Outputs & filters: 
-------------------------------------
  0 - output([INTERSECT([1])]), filter(nil), rowset=256
  1 - output([t1.c2]), filter(nil), rowset=256, 
      access([t1.c2]), partitions(p0)
  2 - output([t1.c2]), filter(nil), rowset=256, 
      access([t1.c2]), partitions(p0)

1 row in set (0.007 sec)
上述示例的执行计划展示中的 outputs & filters 详细列出了 HASH INTERSECT DISTINCT 算子的输出信息,
字段的含义与 MERGE INTERSECT DISTINCT 算子相同。

15.EXCEPT/MINUS

EXCEPT 算子用于对左右子节点算子输出集合进行差集运算,并进行去重。
Oracle 模式下一般使用 MINUS 进行差集运算,MySQL 模式下一般使用 EXCEPT 进行差集运算。
OceanBase 数据库的 MySQL 模式不区分 EXCEPT 和 MINUS,两者均可作为差集运算关键字使用。
OceanBase 数据库支持的 EXCEPT 算子包括 MERGE EXCEPT DISTINCT 和 HASH EXCEPT DISTINCT。
MERGE EXCEPT DISTINCT
如下示例中,Q1 对两个查询使用 MINUS 进行联接, c1 列有可用排序,2 号算子生成了 
MERGE EXCEPT DISTINCT 进行求取差集、去重,由于 c2 列无可用排序,
所以在 4 号算子上分配了 SORT 算子进行排序。算子执行时从左右子节点读取有序输入,
利用有序输入进行 MERGE, 实现去重并得到差集结果

drop table t1;
create table t1(c1 int primary key,c2 int);
insert into t1 values(1,1);
insert into t1 values(2,2);
explain select c1 from t1 minus select c2 from t1\G;
obclient [sjzt]> explain select c1 from t1 minus select c2 from t1\G;
*************************** 1. row ***************************
Query Plan: ================================================
|ID|OPERATOR               |NAME|EST. ROWS|COST|
------------------------------------------------
|0 |EXCHANGE IN REMOTE     |    |2        |5   |
|1 | EXCHANGE OUT REMOTE   |    |2        |5   |
|2 |  MERGE EXCEPT DISTINCT|    |2        |4   |
|3 |   TABLE SCAN          |t1  |2        |2   | #左边的结果有主键,不需要排序
|4 |   SORT                |    |2        |2   | #右边的结果需要排序
|5 |    TABLE SCAN         |t1  |2        |2   |
================================================

Outputs & filters: 
-------------------------------------
  0 - output([EXCEPT([1])]), filter(nil)
  1 - output([EXCEPT([1])]), filter(nil)
  2 - output([EXCEPT([1])]), filter(nil), rowset=256
  3 - output([t1.c1]), filter(nil), rowset=256, 
      access([t1.c1]), partitions(p0)
  4 - output([t1.c2]), filter(nil), rowset=256, sort_keys([t1.c2, ASC])
  5 - output([t1.c2]), filter(nil), rowset=256, 
      access([t1.c2]), partitions(p0)

1 row in set (0.008 sec)

上述示例中,执行计划展示中的 outputs & filters 详细列出了 EXCEPT 算子的输出信息如下:

信息名称	含义
output	该算子的输出表达式。 使用 EXCEPT/MINUS 联接的两个子节点算子对应输出
(Oracle 模式使用 MINUS,MySQL 模式使用 EXCEPT),表示差集运算输出结果中的一列,
括号内部为左右子节点对应此列的输出列。
filter	该算子上的过滤条件。 由于示例中 EXCEPT 算子没有设置 filter,所以为 nil。
HASH EXCEPT DISTINCT
如下示例中,Q2 对两个查询使用 MINUS 进行联接,不可利用排序,
2 号算子使用 HASH EXCEPT DISTINCT 进行求取差集、去重。算子执行时先读取左侧子节点输出建立哈希表并去重,
再读取右侧子节点输出利用哈希表求取差集并去重

explain select c2 from t1 minus select c2 from t1\G;
obclient [sjzt]> explain select c2 from t1 minus select c2 from t1\G;
*************************** 1. row ***************************
Query Plan: ===============================================
|ID|OPERATOR              |NAME|EST. ROWS|COST|
-----------------------------------------------
|0 |EXCHANGE IN REMOTE    |    |2        |6   |
|1 | EXCHANGE OUT REMOTE  |    |2        |5   |
|2 |  HASH EXCEPT DISTINCT|    |2        |5   |
|3 |   TABLE SCAN         |t1  |2        |2   |
|4 |   TABLE SCAN         |t1  |2        |2   |
===============================================

Outputs & filters: 
-------------------------------------
  0 - output([EXCEPT([1])]), filter(nil)
  1 - output([EXCEPT([1])]), filter(nil)
  2 - output([EXCEPT([1])]), filter(nil), rowset=256
  3 - output([t1.c2]), filter(nil), rowset=256, 
      access([t1.c2]), partitions(p0)
  4 - output([t1.c2]), filter(nil), rowset=256, 
      access([t1.c2]), partitions(p0)

1 row in set (0.009 sec)
上述示例的执行计划展示中的 outputs & filters 详细列出了 HASH EXCEPT DISTINCT 算子的输出信息,
字段的含义与 MERGE EXCEPT DISTINCT 算子相同

16.INSERT

INSERT 算子用于将指定的数据插入数据表,数据来源包括直接指定的值和子查询的结果。
OceanBase 数据库支持的 INSERT 算子包括 INSERT 和 MULTI PARTITION INSERT。
INSERT
INSERT 算子用于向数据表的单个分区中插入数据。
如下例所示,Q1 查询将值 (1, '100') 插入到非分区表 t1 中。其中 1 号算子EXPRESSION 用来生成常量表达式的值。

drop table t1; 
drop table t2; 
drop table t3;
create table t1(c1 int primary key,c2 int); 
create table t2(c1 int primary key,c2 int) partition by hash(c1) partitions 10;
create table t3(c1 int primary key,c2 int);
create index idx_t3_c2 on t3(c2) partition by hash(c2) partitions 3;
explain insert into t1 values(1,100)\G;
obclient [sjzt]> explain insert into t1 values(1,100)\G;
*************************** 1. row ***************************
Query Plan: ===========================================
|ID|OPERATOR          |NAME|EST. ROWS|COST|
-------------------------------------------
|0 |DISTRIBUTED INSERT|    |1        |13  |
|1 | EXPRESSION       |    |1        |1   |
===========================================

Outputs & filters: 
-------------------------------------
  0 - output(nil), filter(nil), 
      columns([{t1: ({t1: (t1.c1, t1.c2)})}]), 
      column_values([column_conv(INT,PS:(11,0),NOT NULL,__values.c1)], [column_conv(INT,PS:(11,0),NULL,__values.c2)])
  1 - output([__values.c1], [__values.c2]), filter(nil)
      values({1, 100})

1 row in set (0.030 sec)

上述示例中,执行计划展示中的 outputs & filters 详细列出了 INSERT 算子的输出信息如下:

信息名称	含义
output	该算子输出的表达式。
filter	该算子上的过滤条件。 由于示例中 INSERT 算子没有设置 filter,所以为 nil。
columns	插入操作涉及的数据表的列。
partitions	插入操作涉及到的数据表的分区(非分区表可以认为是一个只有一个分区的分区表)。
更多 INSERT 算子的示例如下:
Q2 查询将值(2, 200)、(3, 300)插入到表 t1 中。

explain insert into t1 values(2,200),(3,300)\G;
obclient [sjzt]> explain insert into t1 values(2,200),(3,300)\G;
*************************** 1. row ***************************
Query Plan: ===========================================
|ID|OPERATOR          |NAME|EST. ROWS|COST|
-------------------------------------------
|0 |DISTRIBUTED INSERT|    |2        |20  |
|1 | EXPRESSION       |    |2        |1   |
===========================================

Outputs & filters: 
-------------------------------------
  0 - output(nil), filter(nil), 
      columns([{t1: ({t1: (t1.c1, t1.c2)})}]), 
      column_values([column_conv(INT,PS:(11,0),NOT NULL,__values.c1)], [column_conv(INT,PS:(11,0),NULL,__values.c2)])
  1 - output([__values.c1], [__values.c2]), filter(nil)
      values({2, 200}, {3, 300})

1 row in set (0.006 sec)


Q3 查询将子查询 SELECT * FROM t3 的结果插入到表 t1 中。
explain insert into t1 select * from t3\G;
obclient [sjzt]> explain insert into t1 select * from t3\G;
*************************** 1. row ***************************
Query Plan: ======================================================
|ID|OPERATOR          |NAME           |EST. ROWS|COST|
------------------------------------------------------
|0 |DISTRIBUTED INSERT|               |1        |15  |
|1 | SUBPLAN SCAN     |ANONYMOUS_VIEW1|1        |2   |
|2 |  TABLE SCAN      |t3             |1        |2   |
======================================================

Outputs & filters: 
-------------------------------------
  0 - output(nil), filter(nil), 
      columns([{t1: ({t1: (t1.c1, t1.c2)})}]), partitions(p0), 
      column_values([column_conv(INT,PS:(11,0),NOT NULL,ANONYMOUS_VIEW1.c1)], [column_conv(INT,PS:(11,0),NULL,ANONYMOUS_VIEW1.c2)])
  1 - output([ANONYMOUS_VIEW1.c1], [ANONYMOUS_VIEW1.c2]), filter(nil), rowset=256, 
      access([ANONYMOUS_VIEW1.c1], [ANONYMOUS_VIEW1.c2])
  2 - output([t3.c1], [t3.c2]), filter(nil), rowset=256, 
      access([t3.c1], [t3.c2]), partitions(p0)

1 row in set (0.069 sec)
Q4 查询将值(1, '100')插入到分区表 t2 中,通过 partitions 参数可以看出,该值会被插入到 t2 的 p5 分区。
explain insert into t2 values(1,100)\G;
obclient [sjzt]> explain insert into t2 values(1,100)\G;
*************************** 1. row ***************************
Query Plan: ===========================================
|ID|OPERATOR          |NAME|EST. ROWS|COST|
-------------------------------------------
|0 |DISTRIBUTED INSERT|    |1        |13  |
|1 | EXPRESSION       |    |1        |1   |
===========================================

Outputs & filters: 
-------------------------------------
  0 - output(nil), filter(nil), 
      columns([{t2: ({t2: (t2.c1, t2.c2)})}]), 
      column_values([column_conv(INT,PS:(11,0),NOT NULL,__values.c1)], [column_conv(INT,PS:(11,0),NULL,__values.c2)])
  1 - output([__values.c1], [__values.c2]), filter(nil)
      values({1, 100})

1 row in set (0.005 sec)
MULTI PARTITION INSERT
MULTI PARTITION INSERT 算子用于向数据表的多个分区中插入数据。


explain insert into t2 values(2,200),(3,300)\G;
obclient [sjzt]> explain insert into t2 values(2,200),(3,300)\G;
*************************** 1. row ***************************
Query Plan: ===========================================
|ID|OPERATOR          |NAME|EST. ROWS|COST|
-------------------------------------------
|0 |DISTRIBUTED INSERT|    |2        |20  |
|1 | EXPRESSION       |    |2        |1   |
===========================================

Outputs & filters: 
-------------------------------------
  0 - output(nil), filter(nil), 
      columns([{t2: ({t2: (t2.c1, t2.c2)})}]), 
      column_values([column_conv(INT,PS:(11,0),NOT NULL,__values.c1)], [column_conv(INT,PS:(11,0),NULL,__values.c2)])
  1 - output([__values.c1], [__values.c2]), filter(nil)
      values({2, 200}, {3, 300})

1 row in set (0.002 sec)
上述示例的执行计划展示中的 outputs & filters 详细列出了 
MULTI PARTITION INSERT 算子的信息,字段的含义与 INSERT 算子相同。

更多 MULTI PARTITION INSERT 算子的示例如下:

Q6 查询将子查询 SELECT * FROM t3 的结果插入到分区表 t2 中,因为无法确定子查询的结果集,
因此数据可能插入到 t2 的 p0 到 p9 的任何一个分区中。从1 号算子可以看到,
这里的 SELECT * FROM t3 会被放在一个子查询中,并将子查询命名为 VIEW1。
当 OceanBase 数据库内部改写 SQL 产生了子查询时,会自动为子查询命名,
并按照子查询生成的顺序命名为 VIEW1、VIEW2、VIEW3...
explain insert into t2 select  * from t3\G;
obclient [sjzt]> explain insert into t2 select  * from t3\G;
*************************** 1. row ***************************
Query Plan: ======================================================
|ID|OPERATOR          |NAME           |EST. ROWS|COST|
------------------------------------------------------
|0 |DISTRIBUTED INSERT|               |1        |15  |
|1 | SUBPLAN SCAN     |ANONYMOUS_VIEW1|1        |2   |
|2 |  TABLE SCAN      |t3             |1        |2   |
======================================================

Outputs & filters: 
-------------------------------------
  0 - output(nil), filter(nil), 
      columns([{t2: ({t2: (t2.c1, t2.c2)})}]), partitions(p[0-9]), 
      column_values([column_conv(INT,PS:(11,0),NOT NULL,ANONYMOUS_VIEW1.c1)], [column_conv(INT,PS:(11,0),NULL,ANONYMOUS_VIEW1.c2)])
  1 - output([ANONYMOUS_VIEW1.c1], [ANONYMOUS_VIEW1.c2]), filter(nil), rowset=256, 
      access([ANONYMOUS_VIEW1.c1], [ANONYMOUS_VIEW1.c2])
  2 - output([t3.c1], [t3.c2]), filter(nil), rowset=256, 
      access([t3.c1], [t3.c2]), partitions(p0)

1 row in set (0.065 sec)

Q7 查询将值(1, '100')插入到非分区表 t3 中。虽然 t3 本身是一个非分区表,
但因为 t3 上存在全局索引 idx_t3_c2,因此本次插入也涉及到了多个分区。
explain insert into t3 values(1,100)\G;
obclient [sjzt]> explain insert into t3 values(1,100)\G;
*************************** 1. row ***************************
Query Plan: ===========================================
|ID|OPERATOR          |NAME|EST. ROWS|COST|
-------------------------------------------
|0 |DISTRIBUTED INSERT|    |1        |20  |
|1 | EXPRESSION       |    |1        |1   |
===========================================

Outputs & filters: 
-------------------------------------
  0 - output(nil), filter(nil), 
      columns([{t3: ({t3: (t3.c1, t3.c2)}, {idx_t3_c2: (t3.c2, t3.c1)})}]), 
      column_values([column_conv(INT,PS:(11,0),NOT NULL,__values.c1)], [column_conv(INT,PS:(11,0),NULL,__values.c2)])
  1 - output([__values.c1], [__values.c2]), filter(nil)
      values({1, 100})

1 row in set (0.003 sec)

17.DELETE

DELETE 算子用于删除数据表中满足指定条件的数据行。
OceanBase 数据库支持的 DELETE 算子包括 DELETE 和 MULTI PARTITION DELETE。
DELETE
DELETE 算子用于删除数据表单个分区中的数据。
如下例所示,Q1 查询删除了表 t1 中所有满足 c2>'100' 的行。

drop table t1; 
drop table t2; 
drop table t3; 
create table t1(c1 int primary key,c2 int);
create table t2(c1 int primary key,c2 int) partition by hash(c1) partitions 10;
create table t3(c1 int primary key,c2 int);
create index idx_t3_c2 on t3(c2) partition by hash(c2) partitions 3;
explain delete from t1 where c2>100\G;
obclient [sjzt]> explain delete from t1 where c2>100\G;
*************************** 1. row ***************************
Query Plan: =============================================
|ID|OPERATOR            |NAME|EST. ROWS|COST|
---------------------------------------------
|0 |EXCHANGE IN REMOTE  |    |1        |9   |
|1 | EXCHANGE OUT REMOTE|    |1        |9   |
|2 |  DELETE            |    |1        |9   |
|3 |   TABLE SCAN       |t1  |1        |2   |
=============================================

Outputs & filters: 
-------------------------------------
  0 - output(nil), filter(nil)
  1 - output(nil), filter(nil)
  2 - output(nil), filter(nil), table_columns([{t1: ({t1: (t1.c1, t1.c2)})}])
  3 - output([t1.c1], [t1.c2]), filter([t1.c2 > 100]), rowset=256, 
      access([t1.c1], [t1.c2]), partitions(p0)

1 row in set (0.029 sec)

上述示例中,执行计划展示中的 outputs & filters 详细列出了 DELETE 算子的输出信息如下:
信息名称	含义
output	该算子输出的表达式。
filter	该算子上的过滤条件。由于示例中 DELETE 算子没有设置 filter,所以为 nil。
对于删除语句,WHERE 中的谓词会下推到基表上,比如 Q1 查询中的 c2>'100' 被下推到了 1 号算子上。
table_columns	删除操作涉及的数据表的列。
更多 DELETE 算子的示例如下:

Q2 查询删除 t1 表中的所有数据行。
Q3 查询删除分区表 t2 中满足 c1 = 1 的数据行。
Q4 查询删除分区表 t2 中满足 c2 > '100' 的数据行。从执行计划中可以看到,
DELETE 算子分配在 EXCHANGE 算子下面,因此 2 号和 3 号算子会作为一个任务以分区的粒度进行调度。
在计划执行时, 3 号算子扫描出 t2 表在一个分区中满足 c2 > '100' 的数据,
2 号算子 DELETE 则只会删除相应分区下扫描出的数据。

explain delete from t1\G;
obclient [sjzt]> explain delete from t1\G;
*************************** 1. row ***************************
Query Plan: ====================================
|ID|OPERATOR   |NAME|EST. ROWS|COST|
------------------------------------
|0 |DELETE     |    |1        |14  |
|1 | TABLE SCAN|t1  |1        |2   |
====================================

Outputs & filters: 
-------------------------------------
  0 - output(nil), filter(nil), table_columns([{t1: ({t1: (t1.c1, t1.c2)})}])
  1 - output([t1.c1], [t1.c2]), filter(nil), rowset=256, 
      access([t1.c1], [t1.c2]), partitions(p0)

1 row in set (0.007 sec)
explain delete from t2 where c1=1\G;

obclient [sjzt]> explain delete from t2 where c1=1\G;
*************************** 1. row ***************************
Query Plan: ===================================
|ID|OPERATOR  |NAME|EST. ROWS|COST|
-----------------------------------
|0 |DELETE    |    |1        |15  |
|1 | TABLE GET|t2  |1        |3   |
===================================

Outputs & filters: 
-------------------------------------
  0 - output(nil), filter(nil), table_columns([{t2: ({t2: (t2.c1, t2.c2)})}])
  1 - output([t2.c1], [t2.c2]), filter(nil), rowset=256, 
      access([t2.c1], [t2.c2]), partitions(p1)

1 row in set (0.006 sec)

explain delete from t2 where c2>100\G;
obclient [sjzt]> explain delete from t2 where c2>100\G;
*************************** 1. row ***************************
Query Plan: ====================================================
|ID|OPERATOR               |NAME    |EST. ROWS|COST|
----------------------------------------------------
|0 |PX COORDINATOR         |        |1        |24  |
|1 | EXCHANGE OUT DISTR    |:EX10000|1        |24  |
|2 |  PX PARTITION ITERATOR|        |1        |24  |
|3 |   DELETE              |        |1        |24  |
|4 |    TABLE SCAN         |t2      |1        |17  |
====================================================

Outputs & filters: 
-------------------------------------
  0 - output(nil), filter(nil), rowset=256
  1 - output(nil), filter(nil), rowset=256, dop=1
  2 - output(nil), filter(nil), rowset=256
  3 - output(nil), filter(nil), table_columns([{t2: ({t2: (t2.c1, t2.c2)})}])
  4 - output([t2.c1], [t2.c2]), filter([t2.c2 > 100]), rowset=256, 
      access([t2.c1], [t2.c2]), partitions(p[0-9])

1 row in set (0.009 sec)

MULTI PARTITION DELETE
MULTI PARTITION DELETE 算子用于删除数据表多个分区中的数据。

如下例所示,Q5 查询删除了表 t3 中所有满足 c2 > '100' 的数据行。
虽然 t3 本身是一个非分区表,但因为 t3 上存在全局索引 idx_t3_c2,
因此每一条数据行会存在于多个分区中。

explain delete from t3 where c2>100\G;
obclient [sjzt]> explain delete from t3 where c2>100\G;
*************************** 1. row ***************************
Query Plan: =========================================================
|ID|OPERATOR               |NAME         |EST. ROWS|COST|
---------------------------------------------------------
|0 |DISTRIBUTED DELETE     |             |1        |24  |
|1 | DISTRIBUTED TABLE SCAN|t3(idx_t3_c2)|1        |6   |
=========================================================

Outputs & filters: 
-------------------------------------
  0 - output(nil), filter(nil), table_columns([{t3: ({t3: (t3.c1, t3.c2)}, {idx_t3_c2: (t3.c2, t3.c1)})}])
  1 - output([t3.c1], [t3.c2]), filter(nil), rowset=256, 
      access([t3.c1], [t3.c2]), partitions(p[0-2])

1 row in set (0.029 sec)

上述示例的执行计划展示中的 outputs & filters 详细列出了 MULTI PARTITION DELETE 算子的信息,
字段的含义与 DELETE 算子相同

18.UPDATE

UPDATE 算子用于更新数据表中满足指定条件的数据行。
OceanBase 数据库支持的 UPDATE 算子包括 UPDATE 和 MULTI PARTITION UPDATE。
UPDATE
UPDATE 算子用于更新数据表单个分区中的数据。
如下例所示,Q1 查询更新了表 t1 中所有满足 c2 = '100' 的行,并将 c2 列的值设置为 200
表同上;

explain update t1 set c2=200 where c2=100\G;
obclient [sjzt]> explain update t1 set c2=200 where c2=100\G;
*************************** 1. row ***************************
Query Plan: ====================================
|ID|OPERATOR   |NAME|EST. ROWS|COST|
------------------------------------
|0 |UPDATE     |    |1        |34  |
|1 | TABLE SCAN|t1  |1        |2   |
====================================

Outputs & filters: 
-------------------------------------
  0 - output(nil), filter(nil), table_columns([{t1: ({t1: (t1.c1, t1.c2)})}]),
      update([t1.c2=column_conv(INT,PS:(11,0),NULL,cast(200, INT(-1, 0)))])
  1 - output([t1.c1], [t1.c2]), filter([t1.c2 = 100]), rowset=256, 
      access([t1.c1], [t1.c2]), partitions(p0)

1 row in set (0.003 sec)

上述示例中,执行计划展示中的 outputs & filters 详细列出了 UPDATE 算子的输出信息如下:
信息名称	含义
output	该算子输出的表达式。
filter	该算子上的过滤条件。由于示例中 UPDATE 算子没有 filter,所以为 nil。
对于更新语句,WHERE 中的谓词会下推到基表上,比如 Q1 查询中的 c2 = '100' 被下推到了 1 号算子上。
table_columns	更新操作涉及的数据表的列。
update	更新操作中所有的赋值表达式。
更多 UPDATE 算子的示例如下:
Q2 查询更新 t1 表中的所有数据行,并将 c2 列的值置为 200。
Q3 查询更新分区表 t2 中满足 c1=100 的数据行,并将 c2 列的值置为 150。
Q4 查询更新分区表 t2 中满足 c2 =100 的数据行,并将 c2 列的值置为 rpad(t2.c2, 10, '9')。



explain update t1 set c2=200\G;
obclient [sjzt]> explain update t1 set c2=200\G;
*************************** 1. row ***************************
Query Plan: ====================================
|ID|OPERATOR   |NAME|EST. ROWS|COST|
------------------------------------
|0 |UPDATE     |    |1        |34  |
|1 | TABLE SCAN|t1  |1        |2   |
====================================

Outputs & filters: 
-------------------------------------
  0 - output(nil), filter(nil), table_columns([{t1: ({t1: (t1.c1, t1.c2)})}]),
      update([t1.c2=column_conv(INT,PS:(11,0),NULL,cast(200, INT(-1, 0)))])
  1 - output([t1.c1], [t1.c2]), filter(nil), rowset=256, 
      access([t1.c1], [t1.c2]), partitions(p0)

1 row in set (0.002 sec)

explain update t2 set t2.c2=150 where t2.c1=100\G;
obclient [sjzt]> explain update t2 set t2.c2=150 where t2.c1=100\G;
*************************** 1. row ***************************
Query Plan: ===================================
|ID|OPERATOR  |NAME|EST. ROWS|COST|
-----------------------------------
|0 |UPDATE    |    |1        |35  |
|1 | TABLE GET|t2  |1        |3   |
===================================

Outputs & filters: 
-------------------------------------
  0 - output(nil), filter(nil), table_columns([{t2: ({t2: (t2.c1, t2.c2)})}]),
      update([t2.c2=column_conv(INT,PS:(11,0),NULL,cast(150, INT(-1, 0)))])
  1 - output([t2.c1], [t2.c2]), filter(nil), rowset=256, 
      access([t2.c1], [t2.c2]), partitions(p0)

1 row in set (0.004 sec)
explain update t2 set t2.c2=rpad(t2.c2,10,'9') where t2.c2=100\G;
obclient [sjzt]> explain update t2 set t2.c2=rpad(t2.c2,10,'9') where t2.c2=100\G;
*************************** 1. row ***************************
Query Plan: ====================================================
|ID|OPERATOR               |NAME    |EST. ROWS|COST|
----------------------------------------------------
|0 |PX COORDINATOR         |        |1        |50  |
|1 | EXCHANGE OUT DISTR    |:EX10000|1        |50  |
|2 |  PX PARTITION ITERATOR|        |1        |49  |
|3 |   UPDATE              |        |1        |49  |
|4 |    TABLE SCAN         |t2      |1        |17  |
====================================================

Outputs & filters: 
-------------------------------------
  0 - output(nil), filter(nil), rowset=256
  1 - output(nil), filter(nil), rowset=256, dop=1
  2 - output(nil), filter(nil), rowset=256
  3 - output(nil), filter(nil), table_columns([{t2: ({t2: (t2.c1, t2.c2)})}]),
      update([t2.c2=column_conv(INT,PS:(11,0),NULL,cast(rpad(cast(cast(100, INT(11, 0)), VARCHAR(1048576)), 10, '9'), INT(-1, 0)))])
  4 - output([t2.c1], [t2.c2]), filter([t2.c2 = 100]), rowset=256, 
      access([t2.c1], [t2.c2]), partitions(p[0-9])

1 row in set (0.003 sec)


MULTI PARTITION UPDATE
MULTI PARTITION UPDATE 算子表示更新数据表多个分区中的数据。如下例所示,
Q5 查询更新表 t3 中所有满足 c2 < '100' 的数据行,并将 c2 列的值置为 200。
虽然 t3 本身是一个非分区表,但 t3 表上存在全局索引 idx_t3_c2,因此每一条数据行会存在于多个分区中。

explain update t3 set c2='200' where c2<'100'\G;
obclient [sjzt]> explain update t3 set c2='200' where c2<'100'\G;
*************************** 1. row ***************************
Query Plan: =========================================================
|ID|OPERATOR               |NAME         |EST. ROWS|COST|
---------------------------------------------------------
|0 |DISTRIBUTED UPDATE     |             |1        |61  |
|1 | DISTRIBUTED TABLE SCAN|t3(idx_t3_c2)|1        |6   |
=========================================================

Outputs & filters: 
-------------------------------------
  0 - output(nil), filter(nil), table_columns([{t3: ({t3: (t3.c1, t3.c2)}, {idx_t3_c2: (t3.c2, t3.c1)})}]),
      update([t3.c2=column_conv(INT,PS:(11,0),NULL,cast('200', INT(-1, 0)))])
  1 - output([t3.c1], [t3.c2]), filter(nil), rowset=256, 
      access([t3.c1], [t3.c2]), partitions(p[0-2])

1 row in set (0.011 sec)

更多 MULTI PARTITION UPDATE 的示例如下:
Q6 查询更新分区表 t2 中满足 c1 = 100 的数据行,并将 c1 列的值设置为 101。
因为更新的列是主键列,可能会导致更新后的数据行与更新前的数据行位于不同的分区,
因此需要使用 MULTI PARTITION UPDATE 算子进行更新。

explain update t2 set t2.c1=101 where t2.c1=100\G;
obclient [sjzt]> explain update t2 set t2.c1=101 where t2.c1=100\G;
*************************** 1. row ***************************
Query Plan: ===========================================
|ID|OPERATOR          |NAME|EST. ROWS|COST|
-------------------------------------------
|0 |DISTRIBUTED UPDATE|    |1        |35  |
|1 | TABLE GET        |t2  |1        |3   |
===========================================

Outputs & filters: 
-------------------------------------
  0 - output(nil), filter(nil), table_columns([{t2: ({t2: (t2.c1, t2.c2)})}]),
      update([t2.c1=column_conv(INT,PS:(11,0),NOT NULL,cast(101, INT(-1, 0)))])
  1 - output([t2.c1], [t2.c2]), filter(nil), rowset=256, 
      access([t2.c1], [t2.c2]), partitions(p0)

1 row in set (0.012 sec)

19.EXCHANGE

EXCHANGE 算子用于线程间进行数据交互。
EXCHANGE 算子适用于在分布式场景,一般都是成对出现的,数据源端有一个 OUT 算子,目的端会有一个 IN 算子。
EXCH-IN/OUT
EXCH-IN/OUT 即 EXCHANGE IN/ EXCHANGE OUT 用于将多个分区上的数据汇聚到一起,发送到查询所在的主节点上。

如下例所示,下面的查询中访问了 5 个分区(p0-p4)的数据,其中 1 号算子接受 2 号算子产生的输出,
并将数据传出;0 号算子接收多个分区上 1 号算子产生的输出,并将结果汇总输出。

drop table t; 
create table t(c1 int,c2 int) partition by hash(c1) partitions 5;
explain select *from t\G; 
obclient [sjzt]> explain select *from t\G; 
*************************** 1. row ***************************
Query Plan: ====================================================
|ID|OPERATOR               |NAME    |EST. ROWS|COST|
----------------------------------------------------
|0 |PX COORDINATOR         |        |1        |10  |
|1 | EXCHANGE OUT DISTR    |:EX10000|1        |10  |
|2 |  PX PARTITION ITERATOR|        |1        |9   |
|3 |   TABLE SCAN          |t       |1        |9   |
====================================================

Outputs & filters: 
-------------------------------------
  0 - output([INTERNAL_FUNCTION(t.c1, t.c2)]), filter(nil), rowset=256
  1 - output([INTERNAL_FUNCTION(t.c1, t.c2)]), filter(nil), rowset=256, dop=1
  2 - output([t.c1], [t.c2]), filter(nil), rowset=256
  3 - output([t.c1], [t.c2]), filter(nil), rowset=256, 
      access([t.c1], [t.c2]), partitions(p[0-4])

1 row in set (0.142 sec)

述示例的执行计划展示中的 outputs & filters 详细列出了 EXCH-IN/OUT 算子的输出信息如下:
信息名称	含义
output	该算子输出的表达式。
filter	该算子上的过滤条件。 由于示例中 EXCH-IN/OUT 算子没有设置 filter,所以为 nil。
EXCH-IN/OUT (REMOTE)
EXCH-IN/OUT (REMOTE) 算子用于将远程的数据(单个分区的数据)拉回本地。
如下例所示,在 A 机器上创建了一张非分区表,在 B 机器上执行查询,读取该表的数据。
此时,由于待读取的数据在远程,执行计划中分配了 0 号算子和 1 号算子来拉取远程的数据。
其中,1 号算子在 A 机器上执行,读取 t 表的数据,并将数据传出;0 号算子在 B 机器上执行,
接收 1 号算子产生的输出。

drop table t; 
create table t(c1 int,c2 int);
explain select * from t\G;
obclient [sjzt]> explain select * from t\G;
*************************** 1. row ***************************
Query Plan: =============================================
|ID|OPERATOR            |NAME|EST. ROWS|COST|
---------------------------------------------
|0 |EXCHANGE IN REMOTE  |    |1        |3   |
|1 | EXCHANGE OUT REMOTE|    |1        |3   |  #将远程数据拉回本地汇聚。
|2 |  TABLE SCAN        |t   |1        |2   |
=============================================

Outputs & filters: 
-------------------------------------
  0 - output([t.c1], [t.c2]), filter(nil)
  1 - output([t.c1], [t.c2]), filter(nil)
  2 - output([t.c1], [t.c2]), filter(nil), rowset=256, 
      access([t.c1], [t.c2]), partitions(p0)

1 row in set (0.007 sec)

上述示例的执行计划展示中的 outputs & filters 详细列出了 EXCH-IN/OUT (REMOTE) 算子的输出信息,
字段的含义与 EXCH-IN/OUT 算子相同。

EXCH-IN/OUT (PKEY)
EXCH-IN/OUT (PKEY) 算子用于数据重分区。它通常用于二元算子中,
将一侧孩子节点的数据按照另外一些孩子节点的分区方式进行重分区。
如下示例中,该查询是对两个分区表的数据进行联接,执行计划将 s 表的数据按照 t 表的分区方式进行重分区,
4 号算子的输入是 s 表扫描的结果,对于 s 表的每一行,该算子会根据 t 表的数据分区,
以及根据查询的联接条件,确定一行数据应该发送到哪个节点。
此外,可以看到 3 号算子是一个 EXCHANGE IN MERGE SORT DISTR,它是一个特殊的 EXCHANGE IN 算子,
它用于在汇总多个分区的数据时,会进行一定的归并排序,在这个执行计划中,
3 号算子接收到的每个分区的数据都是按照 c1 列有序排列的,
它会对每个接收到的数据进行归并排序,从而保证结果输出结果也是按照 c1 列有序排列的。

drop table t; 
create table t(c1 int,c2 int) partition by hash(c1) partitions 5;
create table s(c1 int primary key,c2 int)partition by hash(c1)partitions 4; 
explain select * from s,t where s.c1=t.c1\G;
obclient [sjzt]> explain select * from s,t where s.c1=t.c1\G;
*************************** 1. row ***************************
Query Plan: ==========================================================
|ID|OPERATOR                     |NAME    |EST. ROWS|COST|
----------------------------------------------------------
|0 |PX COORDINATOR               |        |1        |19  |
|1 | EXCHANGE OUT DISTR          |:EX10001|1        |18  |
|2 |  HASH JOIN                  |        |1        |17  |
|3 |   EXCHANGE IN DISTR         |        |1        |8   |
|4 |    EXCHANGE OUT DISTR (PKEY)|:EX10000|1        |8   |
|5 |     PX PARTITION ITERATOR   |        |1        |7   |
|6 |      TABLE SCAN             |s       |1        |7   |
|7 |   PX PARTITION ITERATOR     |        |1        |9   |
|8 |    TABLE SCAN               |t       |1        |9   |
==========================================================

Outputs & filters: 
-------------------------------------
  0 - output([INTERNAL_FUNCTION(s.c1, s.c2, t.c1, t.c2)]), filter(nil), rowset=256
  1 - output([INTERNAL_FUNCTION(s.c1, s.c2, t.c1, t.c2)]), filter(nil), rowset=256, dop=1
  2 - output([s.c1], [t.c1], [s.c2], [t.c2]), filter(nil), rowset=256, 
      equal_conds([s.c1 = t.c1]), other_conds(nil)
  3 - output([s.c1], [s.c2]), filter(nil), rowset=256
  4 - (#keys=1, [s.c1]), output([s.c1], [s.c2]), filter(nil), rowset=256, dop=1
  5 - output([s.c1], [s.c2]), filter(nil), rowset=256
  6 - output([s.c1], [s.c2]), filter(nil), rowset=256, 
      access([s.c1], [s.c2]), partitions(p[0-3])
  7 - output([t.c1], [t.c2]), filter(nil), rowset=256
  8 - output([t.c1], [t.c2]), filter(nil), rowset=256, 
      access([t.c1], [t.c2]), partitions(p[0-4])

1 row in set (0.061 sec)

上述示例的执行计划展示中的 outputs & filters 详细列出了 EXCH-IN/OUT (PKEY) 算子的输出信息如下:
信息名称	含义
output	该算子输出的表达式。
filter	该算子上的过滤条件。 由于示例中 EXCH-IN/OUT(PKEY) 算子没有设置 filter,所以为 nil。
pkey	按照哪一列进行重分区。 例如,#keys=1, [s.c1] 表示按照 c1 列重分区。
EXCH-IN/OUT (HASH)
EXCH-IN/OUT (HASH) 算子用于对数据使用一组哈希函数进行重分区。
如下例所示的执行计划中,3-5 号以及 7-8 号是两组使用哈希重分区的 EXCHANGE 算子。
这两组算子的作用是把 t 表和 s 表的数据按照一组新的哈希函数打散成多份,
示例中的哈希列为 t.c2 和 s.c2,这保证了 c2 列取值相同的行会被分发到同一份中。
基于重分区之后的数据,2 号算子 HASH JOIN 会对每一份数据按照 t.c2= s.c2 进行联接。
此外,由于查询中执行了并行度为 2,计划中展示了 dop = 2 (DOP 是 Degree of Parallelism 的缩写)。

drop table t;
drop table s;
create table t(c1 int,c2 int) partition by hash(c1) partitions 4; 
create table s(c1 int,c2 int) partition by hash(c1) partitions 4;
explain select /*+PQ_DISTRIBUTE(@"SEL$1"("TEST.S"@"SEL@1") HASH HASH),PARALLEL(2)*/ * from t,s where t.c2=s.c2\G;
obclient [sjzt]> explain select /*+PQ_DISTRIBUTE(@"SEL$1"("TEST.S"@"SEL@1") HASH HASH),PARALLEL(2)*/
    -> * from t,s where t.c2=s.c2\G;
*************************** 1. row ***************************
Query Plan: ==========================================================
|ID|OPERATOR                     |NAME    |EST. ROWS|COST|
----------------------------------------------------------
|0 |PX COORDINATOR               |        |1        |10  |
|1 | EXCHANGE OUT DISTR          |:EX10002|1        |9   |
|2 |  HASH JOIN                  |        |1        |8   |
|3 |   EXCHANGE IN DISTR         |        |1        |4   |
|4 |    EXCHANGE OUT DISTR (HASH)|:EX10000|1        |4   |
|5 |     PX BLOCK ITERATOR       |        |1        |4   |
|6 |      TABLE SCAN             |t       |1        |4   |
|7 |   EXCHANGE IN DISTR         |        |1        |4   |
|8 |    EXCHANGE OUT DISTR (HASH)|:EX10001|1        |4   |
|9 |     PX BLOCK ITERATOR       |        |1        |4   |
|10|      TABLE SCAN             |s       |1        |4   |
==========================================================

Outputs & filters: 
-------------------------------------
  0 - output([INTERNAL_FUNCTION(t.c1, t.c2, s.c1, s.c2)]), filter(nil), rowset=256
  1 - output([INTERNAL_FUNCTION(t.c1, t.c2, s.c1, s.c2)]), filter(nil), rowset=256, dop=2
  2 - output([t.c2], [s.c2], [t.c1], [s.c1]), filter(nil), rowset=256, 
      equal_conds([t.c2 = s.c2]), other_conds(nil)
  3 - output([t.c2], [t.c1]), filter(nil), rowset=256
  4 - (#keys=1, [t.c2]), output([t.c2], [t.c1]), filter(nil), rowset=256, dop=2
  5 - output([t.c1], [t.c2]), filter(nil), rowset=256
  6 - output([t.c1], [t.c2]), filter(nil), rowset=256, 
      access([t.c1], [t.c2]), partitions(p[0-3])
  7 - output([s.c2], [s.c1]), filter(nil), rowset=256
  8 - (#keys=1, [s.c2]), output([s.c2], [s.c1]), filter(nil), rowset=256, dop=2
  9 - output([s.c1], [s.c2]), filter(nil), rowset=256
  10 - output([s.c1], [s.c2]), filter(nil), rowset=256, 
      access([s.c1], [s.c2]), partitions(p[0-3])

1 row in set (0.014 sec)

其中,PX PARTITION ITERATO 算子用于按照分区粒度迭代数据,详细信息请参见 GI。
上述示例的执行计划展示中的 outputs & filters 详细列出了 EXCH-IN/OUT (HASH) 算子的输出信息如下:
信息名称	含义
output	该算子输出的表达式。
filter	该算子上的过滤条件。 由于示例中 EXCH-IN/OUT (HASH) 算子没有设置 filter,所以为 nil。
pkey	按照哪一列进行哈希重分区。 例如,#keys=1, [s.c2] 表示按照 c2 列进行哈希重分区。
EXCH-IN/OUT(BROADCAST)
EXCH-IN/OUT(BROADCAST) 算子用于对输入数据使用 BROADCAST 的方法进行重分区,它会将数据广播到其他线程上。
如下示例的执行计划中,3-4 号是一组使用 BROADCAST 重分区方式的EXCHANGE 算子。
它会将 t 表的数据广播到每个线程上,s表每个分区的数据都会尝试和被广播的 t 表数据进行联接。

insert into s values(1,1),(2,2),(3,3),(4,4);
explain select /*+PARALLEL(2)*/ * from t,s where t.c2=s.c2\G;
obclient [sjzt]> explain select /*+PARALLEL(2)*/ * from t,s where t.c2=s.c2\G;
*************************** 1. row ***************************
Query Plan: ==========================================================
|ID|OPERATOR                     |NAME    |EST. ROWS|COST|
----------------------------------------------------------
|0 |PX COORDINATOR               |        |1        |10  |
|1 | EXCHANGE OUT DISTR          |:EX10002|1        |9   |
|2 |  HASH JOIN                  |        |1        |8   |
|3 |   EXCHANGE IN DISTR         |        |1        |4   |
|4 |    EXCHANGE OUT DISTR (HASH)|:EX10000|1        |4   |
|5 |     PX BLOCK ITERATOR       |        |1        |4   |
|6 |      TABLE SCAN             |t       |1        |4   |
|7 |   EXCHANGE IN DISTR         |        |1        |4   |
|8 |    EXCHANGE OUT DISTR (HASH)|:EX10001|1        |4   |
|9 |     PX BLOCK ITERATOR       |        |1        |4   |
|10|      TABLE SCAN             |s       |1        |4   |
==========================================================

Outputs & filters: 
-------------------------------------
  0 - output([INTERNAL_FUNCTION(t.c1, t.c2, s.c1, s.c2)]), filter(nil), rowset=256
  1 - output([INTERNAL_FUNCTION(t.c1, t.c2, s.c1, s.c2)]), filter(nil), rowset=256, dop=2
  2 - output([t.c2], [s.c2], [t.c1], [s.c1]), filter(nil), rowset=256, 
      equal_conds([t.c2 = s.c2]), other_conds(nil)
  3 - output([t.c2], [t.c1]), filter(nil), rowset=256
  4 - (#keys=1, [t.c2]), output([t.c2], [t.c1]), filter(nil), rowset=256, dop=2
  5 - output([t.c1], [t.c2]), filter(nil), rowset=256
  6 - output([t.c1], [t.c2]), filter(nil), rowset=256, 
      access([t.c1], [t.c2]), partitions(p[0-3])
  7 - output([s.c2], [s.c1]), filter(nil), rowset=256
  8 - (#keys=1, [s.c2]), output([s.c2], [s.c1]), filter(nil), rowset=256, dop=2
  9 - output([s.c1], [s.c2]), filter(nil), rowset=256
  10 - output([s.c1], [s.c2]), filter(nil), rowset=256, 
      access([s.c1], [s.c2]), partitions(p[0-3])

1 row in set (0.004 sec)

上述示例的执行计划展示中的 outputs & filters 详细列出了 EXCH-IN/OUT (BROADCAST) 算子的信息,
字段的含义与 EXCH-IN/OUT 算子相同

20.GI

GI 算子用于并行执行中,用于按照分区或者按照数据块迭代整张表。
按照迭代数据的粒度划分,GI 算子包括 PX PARTITION ITERATOR 和 PX BLOCK ITERATOR。
PX PARTITION ITERATOR
PX PARTITION ITERATOR 算子用于按照分区粒度迭代数据。

如下示例中,2 号算子按分区粒度迭代出数据
drop table t; 
create table t(c1 int,c2 int)partition by hash(c1) partitions 4; 
create index idx on t(c1);
explain select /*+FULL(t)*/ c1 from t\G;
obclient [sjzt]> explain select /*+FULL(t)*/ c1 from t\G;
*************************** 1. row ***************************
Query Plan: ====================================================
|ID|OPERATOR               |NAME    |EST. ROWS|COST|
----------------------------------------------------
|0 |PX COORDINATOR         |        |1        |8   |
|1 | EXCHANGE OUT DISTR    |:EX10000|1        |8   |
|2 |  PX PARTITION ITERATOR|        |1        |7   |
|3 |   TABLE SCAN          |t       |1        |7   |
====================================================

Outputs & filters: 
-------------------------------------
  0 - output([INTERNAL_FUNCTION(t.c1)]), filter(nil), rowset=256
  1 - output([INTERNAL_FUNCTION(t.c1)]), filter(nil), rowset=256, dop=1
  2 - output([t.c1]), filter(nil), rowset=256
  3 - output([t.c1]), filter(nil), rowset=256, 
      access([t.c1]), partitions(p[0-3])

1 row in set (0.007 sec)

上述示例的执行计划展示中的 outputs & filters 详细列出了 PX PARTITION ITERATOR 算子的输出信息如下:

信息名称	含义
output	该算子输出的表达式。
filter	该算子上的过滤条件。 由于示例中 PX PARTITION ITERATOR 算子没有设置 filter,所以为 nil。
PX BLOCK ITERATOR
PX BLOCK ITERATOR 算子用于按照数据块粒度迭代数据。

相对于 PX PARTITION ITERATOR,PX BLOCK ITERATOR 算子按照数据块迭代的方式粒度更小,
能够切分出更多的任务,支持更高的并行度。

explain select /*+PARALLEL(4)*/ c1 from t\G; 
obclient [sjzt]> explain select /*+PARALLEL(4)*/ c1 from t\G; 
*************************** 1. row ***************************
Query Plan: ================================================
|ID|OPERATOR           |NAME    |EST. ROWS|COST|
------------------------------------------------
|0 |PX COORDINATOR     |        |1        |2   |
|1 | EXCHANGE OUT DISTR|:EX10000|1        |2   |
|2 |  PX BLOCK ITERATOR|        |1        |2   |
|3 |   TABLE SCAN      |t(idx)  |1        |2   |
================================================

Outputs & filters: 
-------------------------------------
  0 - output([INTERNAL_FUNCTION(t.c1)]), filter(nil), rowset=256
  1 - output([INTERNAL_FUNCTION(t.c1)]), filter(nil), rowset=256, dop=4
  2 - output([t.c1]), filter(nil), rowset=256
  3 - output([t.c1]), filter(nil), rowset=256, 
      access([t.c1]), partitions(p[0-3])

1 row in set (0.003 sec)

上述示例的执行计划展示中的 outputs & filters 详细列出了 PX BLOCK ITERATOR 算子的输出信息,
字段的含义与 PX PARTITION ITERATOR 算子相同

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
这段代码是一个方法 `startObserve()` 的定义,它使用了 `IntersectionObserver` 来监听目标元素的可见性变化。让我们逐行解释代码的含义: 1. `this.ob = new IntersectionObserver(...);`:创建了一个 `IntersectionObserver` 对象,并将其赋值给 `this.ob`。`IntersectionObserver` 是一个用于观察目标元素与其祖先元素或视窗交叉状态的接口。 2. `(entries) => { ... }`:一个箭头函数作为参数传递给 `IntersectionObserver` 构造函数。这个箭头函数会在目标元素的可见性发生变化时被调用,参数 `entries` 是一个包含了目标元素的交叉信息的数组。 3. `this.CHANGE_HEAD_SHOW_STYLE(entries[0].isIntersecting);`:调用了一个名为 `CHANGE_HEAD_SHOW_STYLE()` 的方法,并将目标元素的交叉信息的 `isIntersecting` 属性作为参数传递给该方法。这个方法可能会根据目标元素是否与视窗交叉来改变头部的显示样式。 4. `this.showTip = !entries[0].isIntersecting;`:将目标元素的交叉信息的 `isIntersecting` 属性取反,并将结果赋值给 `this.showTip`。这可能用于控制是否显示提示。 5. `{ threshold: 0.1 }`:一个配置对象,指定了交叉比例的阈值。在这个例子,当目标元素与视窗的交叉比例超过或等于 0.1 时,触发交叉事件。 6. `const value = document.querySelector(".main-bottom");`:使用 `document.querySelector()` 方法选择了一个类名为 "main-bottom" 的元素,并将其赋值给变量 `value`。这个元素将被观察器监听其可见性变化。 7. `this.ob.observe(value);`:将目标元素传递给 `IntersectionObserver` 对象的 `observe()` 方法,以开始观察目标元素的可见性变化。 总体来说,这段代码的作用是创建一个 `IntersectionObserver` 对象,并使用它观察一个特定元素的可见性变化。当目标元素与视窗交叉时,会调用对应的回调函数,并根据交叉信息进行相应处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值