DM7执行计划优化系列(一)——执行计划详解

Grrr 2021/01/08 3790 12 0
摘要 本系列主要讲解SQL优化过程中关于执行计划的基础知识,本篇文章通过示例讲解了执行计划描述的执行顺序和各节点的详细信息。

在上一个系列中,我们着重关注的是操作符本身,这一系列,我们将笼统的介绍一下执行计划,希望达到的效果是:无论某个 SQL 能否找到优化的方法,我们至少可以将执行计划中的每一部分与原 SQL 对应起来。

执行计划是优化的重中之重,这里我们主要讲解执行计划的执行顺序以及需要注意的点,为之后的优化打下一定的基础。

首先,执行计划是由各类操作符组成的一颗树,也就是排序好的操作符的展现形式,从内到外依次执行(看执行计划一般看 MANAGER 中执行计划文本,这样看的更详尽一点,还可以将执行计划拷贝到文本编辑工具 UE,NOTEPAD++ 中,这样缩进更为明显)。

一般的执行计划格式为:

OP1 OP2 OP3 OP4 OP5 OP6 OP7 OP8

复制

缩进越多的越先执行,同样缩进的上面的先执行,下面的后执行,上下的优先级高于内外,那么上面这个例子的执行顺序为:

OP3->OP4->OP2->OP7->OP8->OP6->OP5->OP1

我们拟定一个执行计划与上例结构相同的 SQL

CREATE TABLE TEST5(ID INT); CREATE TABLE TEST6(ID INT); CREATE TABLE TEST7(ID INT); CREATE TABLE TEST8(ID INT); INSERT INTO TEST5 VALUES(3); INSERT INTO TEST6 VALUES(4); INSERT INTO TEST7 SELECT LEVEL %100 FROM DUAL CONNECT BY LEVEL < 10000; INSERT INTO TEST8 SELECT LEVEL %100 FROM DUAL CONNECT BY LEVEL < 10000;

复制

执行计划如下

--SEL19 EXPLAIN SELECT /*+NO_USE_CVT_VAR*/* FROM 2 ( 3 SELECT TEST5.ID FROM TEST5,TEST6 WHERE TEST5.ID = TEST6.ID 4 ) 5 A, 6 ( 7 SELECT ID FROM (SELECT TEST7.ID FROM TEST7,TEST8 WHERE TEST7.ID = TEST8.ID) GROUP BY ID 8 ) 9 B 10 WHERE A.ID = B.ID; 1 #NSET2: [70, 1, 16] 2 #PRJT2: [70, 1, 16]; exp_num(2), is_atom(FALSE) 3 #HASH2 INNER JOIN: [70, 1, 16]; KEY_NUM(1); 4 #PRJT2: [0, 1, 8]; exp_num(1), is_atom(FALSE) 5 #HASH2 INNER JOIN: [0, 1, 8]; KEY_NUM(1); 6 #CSCN2: [0, 1, 4]; INDEX33555453(TEST5) 7 #CSCN2: [0, 1, 4]; INDEX33555454(TEST6) 8 #PRJT2: [68, 9801, 8]; exp_num(1), is_atom(FALSE) 9 #HAGR2: [68, 9801, 8]; grp_num(1), sfun_num(0); 10 #PRJT2: [4, 980100, 8]; exp_num(1), is_atom(FALSE) 11 #HASH2 INNER JOIN: [4, 980100, 8]; KEY_NUM(1); 12 #CSCN2: [1, 9999, 4]; INDEX33555455(TEST7) 13 #CSCN2: [1, 9999, 4]; INDEX33555456(TEST8)

复制

这个例子(忽略/+no_use_cvt_var/)的执行计划,我们暂时不关注 PRJT 和 NSET 操作符,只看 SQL 的执行顺序

3 #HASH2 INNER JOIN: [70, 1, 16]; KEY_NUM(1); 5 #HASH2 INNER JOIN: [0, 1, 8]; KEY_NUM(1); 6 #CSCN2: [0, 1, 4]; INDEX33555453(TEST5) 7 #CSCN2: [0, 1, 4]; INDEX33555454(TEST6) 9 #HAGR2: [68, 9801, 8]; grp_num(1), sfun_num(0); 11 #HASH2 INNER JOIN: [4, 980100, 8]; KEY_NUM(1); 12 #CSCN2: [1, 9999, 4]; INDEX33555455(TEST7) 13 #CSCN2: [1, 9999, 4]; INDEX33555456(TEST8)

复制

和前面的简单例子类似,执行顺序为:

6->7->5->12->13->11->9->3

具体的执行过程就是:首先执行 TEST5 和 TEST6 的 HASH 连接,然后执行 TEST7 和 TEST8 的 HASH 连接并将连接结果进行 HASH 分组,最后将两个结果再次进行 HASH 连接得到最终结果集。

这个例子的 SQL 写法比较简单,意义也是非常明确的,读懂 SQL 之后把操作符顺序写下来不会很困难,同样的,只看到这个执行计划,我们需要能想出来这个 SQL 原本是什么样子的。

读懂 SQL 本身是关键,执行计划更多的是起一个提示作用,侧面告诉大家 SQL 需要做什么事情。能正确读取执行计划描述的执行顺序后,我们关注下执行计划各个节点的详细信息:执行计划中所有操作符的后面都会有一个三元组。

如:#CSCN2: [1, 9999, 4]

[1, 9999, 4] 就是我们提到的三元组,其中 3 个数字分别表示该操作符的估算代价、输出行数和涉及数据的行长。
那么 #CSCN2: [1, 9999, 4] 表示的意义为:这是一个全表扫描操作,涉及的行数为 9999 ,每场数据长度为 4 ,整体代价估算为 1 。

一般情况下,三元组中的首项和尾项仅有参考意义,我们只需要关注第二项,第二项的数值越少,表示这个操作符的代价也越小。

我们将三元组中的第二项称为估算行数(card),在复杂查询中,估算行数对于执行计划以及 SQL 性能的影响很大,而估算行数又受统计信息的影响,下一篇文章我们将会详细介绍统计信息及其对估算行数的影响。

评论
后发表回复

作者

文章

阅读量

获赞

扫一扫
联系客服