在上一个系列中,我们着重关注的是操作符本身,这一系列,我们将笼统的介绍一下执行计划,希望达到的效果是:无论某个 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 性能的影响很大,而估算行数又受统计信息的影响,下一篇文章我们将会详细介绍统计信息及其对估算行数的影响。
文章
阅读量
获赞