如果看到了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)]
*/
说明:
如上面的计划所示:在第二个计划里面,第7行的索引定位,虽然是索引定位,但是他在物理上打中了多少数据?——它打中了所有v1=1的数据,也就是说这次定位,它只用上了v1=1这个条件的过滤性;
而在第三个计划里,第6行的索引定位,在物理上,是通过这一次定位的机会,在物理上只打中了同时满足 v2 in (1,5,6) 和 v1=1的数据,打中的数据量大大减少,即在一次定位过程里(宝贵的一次定位机会)充分利用上了这两个条件的过滤性。
上述这两次定位,第二个计划肯定是低效的、第三个计划肯定就更高效了,因为在定位后的后续操作符,处理的数据都小多了。
毕竟有这样一条公理:SQL的效率说的就是中间结果集大小。(注意:说的不是最总结果集)
我们继续在上面构造的数据环境下,分析下面的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)]
文章
阅读量
获赞