本文主要讨论以下几种索引访问方法:
1.索引唯一扫描(INDEX UNIQUE SCAN)
2.索引范围扫描(INDEX RANGE SCAN)
3.索引全扫描(INDEX FULL SCAN)
4.索引跳跃扫描(INDEX SKIP SCAN)
5.索引快速全扫描(INDEX FAST FULL SCAN) 索引唯一扫描(INDEX UNIQUE SCAN)
通过这种索引访问数据的特点是对于某个特定的值只返回一行数据,通常如果在查询谓语中使用UNIQE和PRIMARY KEY索引的列作为条件的时候会选用这种扫描;访问的高度总是索引的高度加一,除了某些特殊的情况,如另外存储的LOB对象。
SQL> set autotrace traceonly explain
SQL> select * from hr.employees where employee_id = 100;
Execution Plan
----------------------------------------------------------
Plan hash value: 1833546154
---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 69 | 1 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| EMPLOYEES | 1 | 69 | 1 (0)| 00:00:01 |
|* 2 | INDEX UNIQUE SCAN | EMP_EMP_ID_PK | 1 | | 0 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("EMPLOYEE_ID"=100)
索引范围扫描(INDEX RANGE SCAN)
谓语中包含将会返回一定范围数据的条件时就会选用索引范围扫描,索引可以是唯一的亦可以是不唯一的;所指定的条件可以是(<,>,LIKE,BETWEEN,=)等运算符,不过使用LIKE的时候,如果使用了通配符%,极有可能就不会使用范围扫描,因为条件过于的宽泛了,下面是一个示例:
SQL> select * from hr.employees where DEPARTMENT_ID = 30;
6 rows selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 2056577954
-------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 6 | 414 | 2 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| EMPLOYEES | 6 | 414 | 2 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | EMP_DEPARTMENT_IX | 6 | | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("DEPARTMENT_ID"=30)
Statistics
----------------------------------------------------------
8 recursive calls
0 db block gets
7 consistent gets
1 physical reads
0 redo size
1716 bytes sent via SQL*Net to client
523 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
6 rows processed
范围扫描的条件需要准确的分析返回数据的数目,范围越大就越可能执行全表扫描;
SQL> select department_id,count(*) from hr.employees group by department_id order by count(*);
DEPARTMENT_ID COUNT(*)
------------- ----------
10 1
40 1
1
70 1
20 2
110 2
90 3
60 5
30 6
100 6
80 34
50 45
12 rows selected.
-- 这里使用数值最多的50来执行范围扫描
SQL> set autotrace traceonly explain
SQL> select * from hr.employees where DEPARTMENT_ID = 50;
45 rows selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 1445457117
-------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 45 | 3105 | 3 (0)| 00:00:01 |
|* 1 | TABLE ACCESS FULL| EMPLOYEES | 45 | 3105 | 3 (0)| 00:00:01 |
-------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("DEPARTMENT_ID"=50)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
10 consistent gets
0 physical reads
0 redo size
4733 bytes sent via SQL*Net to client
545 bytes received via SQL*Net from client
4 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
45 rows processed
-- 创建测试表,以dba_objects表为例
SQL> create table test as select * from dba_objects;
Table created.
-- 创建一个复合索引,这里选取了一个唯一值较少的owner列作为引导列
SQL> create index i_test on test(owner,object_id,object_type) ;
Index created.
-- 分析表收集统计信息
SQL> exec dbms_stats.gather_table_stats('SYS','TEST');
PL/SQL procedure successfully completed.
-- 先看一下引导列的唯一值的比较
SQL> select count(*),count(distinct owner) from test;
COUNT(*) COUNT(DISTINCTOWNER)
---------- --------------------
72482 29
-- 使用非引导列的条件查询来访问触发SKIP SCAN
SQL> select * from test where object_id = 46;
Execution Plan
----------------------------------------------------------
Plan hash value: 1001786056
--------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 97 | 31 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| TEST | 1 | 97 | 31 (0)| 00:00:01 |
|* 2 | INDEX SKIP SCAN | I_TEST | 1 | | 30 (0)| 00:00:01 |
--------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("OBJECT_ID"=46)
filter("OBJECT_ID"=46)
Statistics
----------------------------------------------------------
101 recursive calls
0 db block gets
38 consistent gets
0 physical reads
0 redo size
1610 bytes sent via SQL*Net to client
523 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
3 sorts (memory)
0 sorts (disk)
1 rows processed
-- 来看看这条语句全扫描的效率
SQL> select /*+ full(test) */ * from test where object_id = 46;
Execution Plan
----------------------------------------------------------
Plan hash value: 1357081020
--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 97 | 282 (1)| 00:00:04 |
|* 1 | TABLE ACCESS FULL| TEST | 1 | 97 | 282 (1)| 00:00:04 |
--------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("OBJECT_ID"=46)
Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets
1037 consistent gets
0 physical reads
0 redo size
1607 bytes sent via SQL*Net to client
523 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
分析上面的查询可以看出,我们使用的索引中引导列有29个唯一值,也就是说在执行索引跳跃扫描的时候,分割成了29个逻辑子索引来查询,只产生了38次逻辑读;而相对全表扫描的1037次逻辑读,性能提升非常明显! 索引快速全扫描(INDEX FAST FULL SCAN)
这种访问方法在获取数据上和全表扫描相同,都是通过无序的多块读取来进行的,因此也就无法使用它来避免排序代价了;索引快速全扫描通常发生在查询列都在索引中并且索引中一列有非空约束时,当然这个条件也容易发生索引全扫描,它的存在多可用来代替全表扫描,比较数据获取不需要访问表上的数据块。
-- 依旧使用上面创建的test表
SQL> desc test
Name Null? Type
----------------------------------------- -------- ----------------------------
OWNER VARCHAR2(30)
OBJECT_NAME VARCHAR2(128)
SUBOBJECT_NAME VARCHAR2(30)
OBJECT_ID NOT NULL NUMBER
DATA_OBJECT_ID NUMBER
OBJECT_TYPE VARCHAR2(19)
CREATED DATE
LAST_DDL_TIME DATE
TIMESTAMP VARCHAR2(19)
STATUS VARCHAR2(7)
TEMPORARY VARCHAR2(1)
GENERATED VARCHAR2(1)
SECONDARY VARCHAR2(1)
NAMESPACE NUMBER
EDITION_NAME VARCHAR2(30)
-- 在object_id列上创建索引
SQL> create index pri_inx on test (object_id);
Index created.
-- 直接执行全表扫描
SQL> select object_id from test;
72482 rows selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 1357081020
--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 72482 | 353K| 282 (1)| 00:00:04 |
| 1 | TABLE ACCESS FULL| TEST | 72482 | 353K| 282 (1)| 00:00:04 |
--------------------------------------------------------------------------
Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets
5799 consistent gets
0 physical reads
0 redo size
1323739 bytes sent via SQL*Net to client
53675 bytes received via SQL*Net from client
4834 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
72482 rows processed
-- 修改object_id为not null
SQL> alter table test modify (object_id not null);
Table altered.
-- 再次使用object_id列查询就可以看到使用了快速全扫描了
SQL> select object_id from test;
72482 rows selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 3806735285
--------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 72482 | 353K| 45 (0)| 00:00:01 |
| 1 | INDEX FAST FULL SCAN| PRI_INX | 72482 | 353K| 45 (0)| 00:00:01 |
--------------------------------------------------------------------------------
Statistics
----------------------------------------------------------
167 recursive calls
0 db block gets
5020 consistent gets
161 physical reads
0 redo size
1323739 bytes sent via SQL*Net to client
53675 bytes received via SQL*Net from client
4834 SQL*Net roundtrips to/from client
4 sorts (memory)
0 sorts (disk)
72482 rows processed