当数据库不需要遍历表中的每一行来获取结果时,索引真的会大放异彩。所以COUNT(*) 不是最好的例子。以此为例:
alter session set statistics_level = 'ALL';
create table mytable as select * from all_objects;
select * from mytable where owner = 'SYS' and object_name = 'DUAL';
---------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
---------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 300 |00:00:00.01 | 12 |
| 1 | TABLE ACCESS FULL| MYTABLE | 1 | 19721 | 300 |00:00:00.01 | 12 |
---------------------------------------------------------------------------------------
所以,在这里,数据库进行全表扫描(TABLE ACCESS FULL),这意味着它必须访问数据库中的每一行,这意味着它必须从磁盘加载每个块。大量的 I/O。优化器猜测它会找到 15000 行,但我知道只有一个。
对比一下:
create index myindex on mytable( owner, object_name );
select * from mytable where owner = 'SYS' and object_name = 'JOB$';
select * from table( dbms_xplan.display_cursor( null, null, 'ALLSTATS LAST' ));
----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | Reads |
----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 3 | 2 |
| 1 | TABLE ACCESS BY INDEX ROWID| MYTABLE | 1 | 2 | 1 |00:00:00.01 | 3 | 2 |
|* 2 | INDEX RANGE SCAN | MYINDEX | 1 | 1 | 1 |00:00:00.01 | 2 | 2 |
----------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("OWNER"='SYS' AND "OBJECT_NAME"='JOB$')
这里,因为有一个索引,所以它执行INDEX RANGE SCAN 来查找符合我们条件的表的rowid。然后,它会转到表本身 (TABLE ACCESS BY INDEX ROWID) 并仅查找我们需要的行,并且可以有效地执行此操作,因为它具有 rowid。
更妙的是,如果您恰好要查找完全在索引中的内容,则扫描甚至不必返回基表。索引就够了:
select count(*) from mytable where owner = 'SYS';
select * from table( dbms_xplan.display_cursor( null, null, 'ALLSTATS LAST' ));
------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | Reads |
------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 46 | 46 |
| 1 | SORT AGGREGATE | | 1 | 1 | 1 |00:00:00.01 | 46 | 46 |
|* 2 | INDEX RANGE SCAN| MYINDEX | 1 | 8666 | 9294 |00:00:00.01 | 46 | 46 |
------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("OWNER"='SYS')
因为我的查询涉及所有者列并且它包含在索引中,所以它永远不需要返回基表来查找那里的任何内容。所以索引扫描就足够了,然后它会进行聚合来计算行数。这种情况有点不完美,因为索引在 (owner, object_name) 上,而不仅仅是所有者,但它绝对比在主表上进行全表扫描要好。