注册
执行计划和达梦优化的基础篇| 和优化器做朋友

执行计划和达梦优化的基础篇| 和优化器做朋友

hql 2021/12/09 4996 13 0
摘要 首先我们要看懂执行计划:具体操作 | 我们已经知道有一个语句慢了,怎么处理?(行业黑话:看下ET和执行计划) | 达梦技术社区 https://eco.dameng.com/community/article/015dcd7d940f9325ee2a5af93256cc33 其次,我们还要懂得这个文章的内容,那就是一个合格的语句效率问题砖瓦工啦。

执行计划是什么?

image.png

什么叫走了索引?

image.png

如果看到了CSCN和SSCN这个都叫做扫描,都不能叫做我们平常说的“走了索引”;看到这两个操作符,就认为这个语句有问题就可以了(这么认为,一般都是对的。)

结合上面图示的B+树进行理解,一般就是说:从上往下的叫做定位,很快;最底层,从左往右叫做扫描,相对来说,慢的跟个老乌龟似的。

走了索引就可以了吗?可不可以效率更好一点?我们如何快准狠的判断?

其实很简单,我们看下下面的例子:

-- 构造数据 drop table if exists test1; create table test1(v1 int,v2 int); insert into test1 select mod(level,10),mod(level,1000) from dual connect by level<=1000000; commit; --建立索引 ----目标sql select count(*) from test1 where v2 in (1,5,6) and v1=1; /* 执行计划如下,看到CSCN确实没有走索引,执行耗时 51毫秒 1 #NSET2: [70, 1, 8] 2 #PRJT2: [70, 1, 8]; exp_num(1), is_atom(FALSE) 3 #AAGR2: [70, 1, 8]; grp_num(0), sfun_num(1), distinct_flag[0]; slave_empty(0) 4 #HASH RIGHT SEMI JOIN2: [70, 1875, 8]; n_keys(1) KEY(DMTEMPVIEW_16778780.colname=TEST1.V2) KEY_NULL_EQU(0) 5 #CONST VALUE LIST: [1, 3, 4]; row_num(3), col_num(1), 6 #BLKUP2: [70, 25000, 8]; IDX_TEST1_V1(TEST1) 7 #SSEK2: [70, 25000, 8]; scan_type(ASC), IDX_TEST1_V1(TEST1), scan_range[1,1] */ ----建立索引 create index idx_test1_v1 on test1(v1); select count(*) /**/from test1 where v2 in (1,5,6) and v1=1; /* 执行计划如下,看到CSCN确实没有走索引,执行耗时 271毫秒 1 #NSET2: [70, 1, 8] 2 #PRJT2: [70, 1, 8]; exp_num(1), is_atom(FALSE) 3 #AAGR2: [70, 1, 8]; grp_num(0), sfun_num(1), distinct_flag[0]; slave_empty(0) 4 #HASH RIGHT SEMI JOIN2: [70, 1875, 8]; n_keys(1) KEY(DMTEMPVIEW_16778829.colname=TEST1.V2) KEY_NULL_EQU(0) 5 #CONST VALUE LIST: [1, 3, 4]; row_num(3), col_num(1), 6 #BLKUP2: [70, 25000, 8]; IDX_TEST1_V1(TEST1) 7 #SSEK2: [70, 25000, 8]; scan_type(ASC), IDX_TEST1_V1(TEST1), scan_range[1,1] */ ----如果建立合适的索引呢? create index idx_test1_v1v2 on test1(v1,v2); select count(*) /*2*/from test1 where v2 in (1,5,6) and v1=1; /* 执行计划如下,看到CSCN确实没有走索引,执行耗时 2 毫秒 1 #NSET2: [1, 1, 8] 2 #PRJT2: [1, 1, 8]; exp_num(1), is_atom(FALSE) 3 #AAGR2: [1, 1, 8]; grp_num(0), sfun_num(1), distinct_flag[0]; slave_empty(0) 4 #NEST LOOP INDEX JOIN2: [1, 1875, 8] 5 #CONST VALUE LIST: [1, 3, 4]; row_num(3), col_num(1), 6 #SSEK2: [1, 625, 8]; scan_type(ASC), IDX_TEST1_V1V2(TEST1), scan_range[(1,DMTEMPVIEW_16778840.colname),(1,DMTEMPVIEW_16778840.colname)] */

说明:

  1. 图中共有三个计划
  2. 走没走索引当然重要,但是走了索引的时候,其过滤性如何,更重要!
  3. 在一个语句当中,一个表被引用时,他只有一次的定位机会,这次定位,在物理上会打中多少数据的少多,直接决定了这个语句效率的高低,这是最最最关键的部分。—— 这里我也叫做,在表的一次定位中,到底用上了哪些条件的过滤性。

如上面的计划所示:在第二个计划里面,第7行的索引定位,虽然是索引定位,但是他在物理上打中了多少数据?——它打中了所有v1=1的数据,也就是说这次定位,它只用上了v1=1这个条件的过滤性;
而在第三个计划里,第6行的索引定位,在物理上,是通过这一次定位的机会,在物理上只打中了同时满足 v2 in (1,5,6) 和 v1=1的数据,打中的数据量大大减少,即在一次定位过程里(宝贵的一次定位机会)充分利用上了这两个条件的过滤性。

上述这两次定位,第二个计划肯定是低效的、第三个计划肯定就更高效了,因为在定位后的后续操作符,处理的数据都小多了。

毕竟有这样一条公理:SQL的效率说的就是中间结果集大小。(注意:说的不是最总结果集)

  1. 关于上面第三点里,关于第三个计划的描述,有的同学可能不太了解;实际上,大家主要理解:在第三个计划中,第五航的 const value list里面有3行数据,就是 v2 in的那三个值(分别为1 5 6),通过低6 行的索引定位上的seek,这个索引定位的 scan_rang里面,是有两个并排在一起的过滤条件(并排在一起就表示同时生效了),分别为1和DMTEMPVIEW_16778840.colname ,而DMTEMPVIEW_16778840.colname就是左子树 const value list 传递下来的 1或者5或者6.

番外篇

我们继续在上面构造的数据环境下,分析下面的SQL和执行计划:

select/*+no_index(test1 IDX_TEST1_V1V2)*/ * from test1 where v1 =1 and v2=4; /* 计划如下: 1 #NSET2: [71, 625, 16] 2 #PRJT2: [71, 625, 16]; exp_num(3), is_atom(FALSE) 3 #SLCT2: [71, 625, 16]; TEST1.V2 = 4 4 #BLKUP2: [71, 25000, 16]; IDX_TEST1_V1(TEST1) 5 #SSEK2: [71, 25000, 16]; scan_type(ASC), IDX_TEST1_V1(TEST1), scan_range[1,1] */

在上述计划中,如果看到了形如第三行:slct2 的尾巴上,还有过滤条件的:
1)如果我们通过业务知道这个过滤前后,数据没什么减少,也就算了
2)如果我们明显知道这个过滤条件,过滤前后,数据量会被减少非常多

那么那么,一定要把这个过滤条件下放到所有条件里面去!!!

也就是希望让它的计划变为:

1   #NSET2: [1, 625, 16] 
2     #PRJT2: [1, 625, 16]; exp_num(3), is_atom(FALSE) 
3       #SSEK2: [1, 625, 16]; scan_type(ASC), IDX_TEST1_V1V2(TEST1), scan_range[(1,4),(1,4)]
评论
后发表回复

作者

文章

阅读量

获赞

扫一扫
联系客服