索引列顺序对索引的影响
最近工作中经常发现一些执行计划走的和自己意识里不太一样的操作,比如复合索引当前导列使用访问查询时。
当然,如果复合索引列能直接在访问索引时通过access访问,那么索引列顺序确实对执行计划没有影响。但是
access是根据查询条件直接定位到记录,filter是在获得的记录上应用限制条件去过滤。
如果优化器能通过access直接定位到记录,那么效率将是比较高的。
测试数据:
create table test1 as select object_id,object_name,owner from dba_objects;
create table test2 as select object_id,object_name,owner from dba_objects;
create index i_test1_1 on test1(object_id,owner);
一、索引中包含范围查询
1、范围查询是前导列,动态采样收集统计信息(没有手动收集)
SQL> explain plan for select /*+use_nl(test2,test1)*/ *
2 from test1, test2
3 where test1.object_id >= 100
4 and test1.object_id <= 10000
5 and test1.owner = test2.owner;
已解释。
SQL> select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 1336851562
------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 33M| 6128M| 190K (1)| 00:38:12 |
| 1 | NESTED LOOPS | | | | | |
| 2 | NESTED LOOPS | | 33M| 6128M| 190K (1)| 00:38:12 |
| 3 | TABLE ACCESS FULL | TEST2 | 77487 | 7264K| 117 (1)| 00:00:02 |
|* 4 | INDEX RANGE SCAN | I_TEST1_1 | 27 | | 2 (0)| 00:00:01 |
| 5 | TABLE ACCESS BY INDEX ROWID| TEST1 | 432 | 41472 | 3 (0)| 00:00:01 |
------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
4 - access("TEST1"."OBJECT_ID">=100 AND "TEST1"."OWNER"="TEST2"."OWNER" AND
"TEST1"."OBJECT_ID"<=10000)
filter("TEST1"."OWNER"="TEST2"."OWNER")
Note
-----
- dynamic sampling used for this statement (level=2)
已选择23行。
手动收集统计信息:
analyze table test1 compute statistics for table for all indexes for all indexed columns;
analyze table test2 compute statistics for table for all indexes for all indexed columns;
SQL> explain plan for select /*+use_nl(test2,test1)*/ *
2 from test1, test2
3 where test1.object_id >= 100
4 and test1.object_id <= 10000
5 and test1.owner = test2.owner;
已解释。
SQL> select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 3297033873
------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 23M| 1685M| 1100K (1)| 03:40:12 |
| 1 | NESTED LOOPS | | 23M| 1685M| 1100K (1)| 03:40:12 |
| 2 | TABLE ACCESS BY INDEX ROWID| TEST1 | 9608 | 356K| 88 (0)| 00:00:02 |
|* 3 | INDEX RANGE SCAN | I_TEST1_1 | 9608 | | 31 (0)| 00:00:01 |
|* 4 | TABLE ACCESS FULL | TEST2 | 2421 | 91998 | 115 (1)| 00:00:02 |
------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("TEST1"."OBJECT_ID">=100 AND "TEST1"."OBJECT_ID"<=10000)
4 - filter("TEST1"."OWNER"="TEST2"."OWNER")
已选择17行。
统计信息收集前后,前者是access和filter后缀索引,后者直接只选择了前导索引。
也就是说这两者都是通过object_id采用access方式去定位索引扫描的起点,然后将根据限制条件进行filter过滤。
2、test1在(owner,object_id)上建索引
SQL> explain plan for select /*+use_nl(test2,test1)*/ *
2 from test1, test2
3 where test1.object_id >= 100
4 and test1.object_id <= 10000
5 and test1.owner = test2.owner;
已解释。
SQL> select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 2279085159
------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 23M| 1685M| 73726 (1)| 00:14:45 |
| 1 | NESTED LOOPS | | | | | |
| 2 | NESTED LOOPS | | 23M| 1685M| 73726 (1)| 00:14:45 |
| 3 | TABLE ACCESS FULL | TEST2 | 72617 | 2694K| 117 (1)| 00:00:02 |
|* 4 | INDEX RANGE SCAN | I_TEST1_2 | 4 | | 1 (0)| 00:00:01 |
| 5 | TABLE ACCESS BY INDEX ROWID| TEST1 | 320 | 12160 | 2 (0)| 00:00:01 |
------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
4 - access("TEST1"."OWNER"="TEST2"."OWNER" AND "TEST1"."OBJECT_ID">=100 AND
"TEST1"."OBJECT_ID"<=10000)
已选择18行。
二、如果等值条件会怎样?
1、索引I_TEST1_1(object_id,owner)
SQL> set autot trace
SQL> select /*+use_nl(test2,test1)*/ *
2 from test1, test2
3 where test1.object_id=test2.object_id
4 and test1.object_id=10
5 and test1.owner=test2.owner
6 and test1.owner='SYS';
执行计划
----------------------------------------------------------
Plan hash value: 3297033873
------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 3 | 228 | 119 (1)| 00:00:02 |
| 1 | NESTED LOOPS | | 3 | 228 | 119 (1)| 00:00:02 |
| 2 | TABLE ACCESS BY INDEX ROWID| TEST1 | 1 | 38 | 2 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | I_TEST1_1 | 1 | | 1 (0)| 00:00:01 |
|* 4 | TABLE ACCESS FULL | TEST2 | 7 | 266 | 117 (1)| 00:00:02 |
------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("TEST1"."OBJECT_ID"=10 AND "TEST1"."OWNER"='SYS')
4 - filter("TEST2"."OBJECT_ID"=10 AND "TEST2"."OWNER"='SYS')
统计信息
----------------------------------------------------------
1 recursive calls
0 db block gets
413 consistent gets
0 physical reads
0 redo size
762 bytes sent via SQL*Net to client
416 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
2、I_TEST1_2(owner,object_id)
SQL> alter index i_test1_1 unusable;
索引已更改。
SQL> alter index i_test1_2 rebuild;
索引已更改。
SQL> analyze table test1 compute statistics for table for all indexes for all indexed columns;
表已分析。
SQL> select /*+use_nl(test2,test1)*/ *
2 from test1, test2
3 where test1.object_id=test2.object_id
4 and test1.object_id=10
5 and test1.owner=test2.owner
6 and test1.owner='SYS';
执行计划
----------------------------------------------------------
Plan hash value: 1354526383
------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 3 | 228 | 119 (1)| 00:00:02 |
| 1 | NESTED LOOPS | | 3 | 228 | 119 (1)| 00:00:02 |
| 2 | TABLE ACCESS BY INDEX ROWID| TEST1 | 1 | 38 | 2 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | I_TEST1_2 | 1 | | 1 (0)| 00:00:01 |
|* 4 | TABLE ACCESS FULL | TEST2 | 7 | 266 | 117 (1)| 00:00:02 |
------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("TEST1"."OWNER"='SYS' AND "TEST1"."OBJECT_ID"=10)
4 - filter("TEST2"."OBJECT_ID"=10 AND "TEST2"."OWNER"='SYS')
统计信息
----------------------------------------------------------
1 recursive calls
0 db block gets
413 consistent gets
0 physical reads
0 redo size
762 bytes sent via SQL*Net to client
416 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
从上面看,等值条件时,不管前导列选择性好坏,性能上没有什么差别。
下面这段文字摘自 yangtingkun博客,学习这种分析方式,向大师致敬。 http://blog.itpub.net/post/468/509328
为什么当出现了前缀字段的范围查询,后缀字段的查询条件就失去意义呢,这与索引的结构有关。索引的树形结构使得索引支持等值查询,也就是通过索引的根节点、树枝节点最终定位到索引的叶节点。而索引的相邻叶节点直接是双向链表结构,使得索引范围扫描可以实现。当索引根据前缀字段开始范围扫描时,显然没有办法根据后缀字段的值在链表结构中跳跃执行,因此后缀的限制条件只能变成FILTER过滤条件。