####4.2.2 索引访问oracle
当oracle须要访问索引里的某个索引条目时,oracle是如何找到该索引条目所在的数据块的呢?优化
当oracle进程须要访问数据文件里的数据块时,oracle会有两种类型的I/O操做方式:code
- 随机访问,每次读取一个数据块(经过等待事件“db file sequential read”体现出来)。
- 顺序访问,每次读取多个数据块(经过等待事件“db file scattered read”体现出来)。
第一种方式则是访问索引里的数据块,而第二种方式的I/O操做属于全表扫描。这里顺带有一个问题,为什么随机访问会对应到db file sequential read等待事件,而顺序访问则会对应到db file scattered read等待事件呢?这彷佛反过来了,随机访问才应该是分散(scattered)的,而顺序访问才应该是顺序(sequential)的。其实,等待事件主要根据实际获取物理I/O块的方式来命名的,而不是根据其在I/O子系统的逻辑方式来命名的。下面对于如何获取索引数据块的方式中会对此进行说明。对象
咱们看到前面对B树索引的体系结构的描述,能够知道其为一个树状的立体结构。其对应到数据文件里的排序
排列固然仍是一个平面的形式,也就是像下面这样。所以,当oracle须要访问某个索引块的时候,势必会在这个结构上跳跃的移动。索引
/根/分支/分支/叶子/…/叶子/分支/叶子/叶子/…/叶子/分支/叶子/叶子/…/叶子/分支/.....进程
当oracle须要得到一个索引块时,首先从根节点开始,根据所要查找的键值,从而知道其所在的下一层的分支节点,而后访问下一层的分支节点,再次一样根据键值访问再下一层的分支节点,如此这般,最终访问到最底层的叶子节点。能够看出,其得到物理I/O块时,是一个接着一个,按照顺序,串行进行的。在得到最终物理块的过程当中,咱们不能同时读取多个块,由于咱们在没有得到当前块的时候是不知道接下来应该访问哪一个块的。所以,在索引上访问数据块时,会对应到db file sequential read等待事件,其根源在于咱们是按照顺序从一个索引块跳到另外一个索引块,从而找到最终的索引块的。事件
那么对于全表扫描来讲,则不存在访问下一个块以前须要先访问上一个块的状况。全表扫描时,oracle知道要访问全部的数据块,所以惟一的问题就是尽量高效的访问这些数据块。所以,这时oracle能够采用同步的方式,分几批,同时获取多个数据块。这几批的数据块在物理上多是分散在表里的,所以其对应到db file scattered read等待事件。ip
#####4.2.2.1 索引惟一扫描同步
当使用
UNIQUE或者PRIMARY KEY
索引的列做为条件时就会选用索引惟一扫描。这种类型的索引可以保证对于某个特定的值只返回一行数据。这种状况下,索引结构将会被从根到叶子进行遍历直到某个条目,取出其行编号,而后使用这个行编号访问包含这一行的表数据块。TABLE ACCESS BY INDEX ROWID
代表了对于表数据块的访问。除非在某些特定的状况下(例如,数据行是链式的或者包含存储在别处的大对象),不然须要访问的数据块数老是等于索引的高度+1。
SQL> select employee_id, last_name from employees where employee_id = 200; --------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 12 | 1 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID| EMPLOYEES | 1 | 12 | 1 (0)| 00:00:01 | |* 2 | INDEX UNIQUE SCAN | EMP_EMP_ID_PK | 1 | | 0 (0)| 00:00:01 | ---------------------------------------------------------------------------------------------
#####4.2.2.2 索引范围扫描
包含必定范围的条件时,就会选用索引范围扫描。索引能够是惟一或者不惟一的,由于是有条件来肯定是否返回多个数据行的。所指定的条件如: < , >, like, 甚至是 = 运算。为了可以选用索引范围扫描,须要至关仔细地选择范围,范围越大就越有可能使用全表扫描代替索引范围扫描。
SQL> select employee_id, last_name from employees where department_id = 20; ------------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 2 | 30 | 2 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID| EMPLOYEES | 2 | 30 | 2 (0)| 00:00:01 | |* 2 | INDEX RANGE SCAN | EMP_DEPARTMENT_IX | 2 | | 1 (0)| 00:00:01 | ------------------------------------------------------------------------------------------------- SQL> select employee_id, last_name from employees where employee_id between 200 and 250; --------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 6 | 72 | 2 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID| EMPLOYEES | 6 | 72 | 2 (0)| 00:00:01 | |* 2 | INDEX RANGE SCAN | EMP_EMP_ID_PK | 6 | | 1 (0)| 00:00:01 | ---------------------------------------------------------------------------------------------
注意: 可以使用一个升序排列的索引(默认值)来返回降序排列的数据行。即便全表可能更合理,优化器也有可能会选择使用索引顺序存储的,
SQL> select employee_id, last_name from employees where department_id in (90, 100) order by department_id desc; ----------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes |Cost (%CPU)|Time | ----------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 9 | 135 | 2 (0)| 00:00:01 | | 1 | INLIST ITERATOR | | | | | | | 2 | TABLE ACCESS BY INDEX ROWID | EMPLOYEES | 9 | 135 | 2 (0)| 00:00:01 | |* 3 | INDEX RANGE SCAN DESCENDING| EMP_DEPARTMENT_IX | 9 | | 1 (0)| 00:00:01 | -----------------------------------------------------------------------------------------------
#####4.2.2.3 索引全扫描
- 当没有条件,可是所需列的列表能够经过其中一列的索引得到;
- 当须要查询某一列的最小或最大值;
SQL> select email from employees; --------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 109 | 872 | 1 (0)| 00:00:01 | | 1 | INDEX FULL SCAN | EMP_EMAIL_UK | 109 | 872 | 1 (0)| 00:00:01 | --------------------------------------------------------------------------------- SQL> select min(department_id) from employees; ------------------------------------------------------------------------------------------------ | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | 1 | 3 | 1 (0)| 00:00:01 | | 1 | SORT AGGREGATE | | 1 | 3 | | | | 2 | INDEX FULL SCAN (MIN/MAX)| EMP_DEPARTMENT_IX | 1 | 3 | 1 (0)| 00:00:01 | ------------------------------------------------------------------------------------------------
#####4.2.2.4 索引跳跃扫描
优化器根据索引中的前导列(索引到的第一列)的惟一值的数量决定是否使用skip scan.
#实例一 SQL> create table test_objects as 2 select * from all_objects; Table created. SQL> create index test_objects_i on test_objects(owner, object_name, subobject_name); Index created. SQL> exec dbms_stats.gather_table_stats(user, 'TEST_OBJECTS', cascade => true); PL/SQL procedure successfully completed. SQL> set autotrace traceonly; SQL> select owner, object_name 2 from test_objects 3 where owner = 'SYS' 4 and object_name = 'DBMS_OUTPUT'; ----------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ----------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 31 | 3 (0)| 00:00:01 | |* 1 | INDEX RANGE SCAN| TEST_OBJECTS_I | 1 | 31 | 3 (0)| 00:00:01 | ----------------------------------------------------------------------------------- SQL> select owner, object_name 2 from test_objects 3 where object_name = 'DBMS_OUTPUT'; ----------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ----------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 2 | 62 | 45 (0)| 00:00:01 | |* 1 | INDEX SKIP SCAN | TEST_OBJECTS_I | 2 | 62 | 45 (0)| 00:00:01 | -----------------------------------------------------------------------------------
#####4.2.2.5 索引快速全扫描
当索引自己包含查询中指定的全部列时,Oracle执行索引快速全扫描。索引快速全扫描和索引全扫描的区别在于:索引全扫描使用单块读操做,而索引快速全扫描使用多块读。这种扫描不能用于避免排序,由于数据块是经过无序的多块读取来读取的。
SQL> create table t as select * from all_objects; Table created. SQL> select count(*) from t where owner = 'SCOTT'; --------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 17 | 296 (1)| 00:00:04 | | 1 | SORT AGGREGATE | | 1 | 17 | | | |* 2 | TABLE ACCESS FULL| T | 17 | 289 | 296 (1)| 00:00:04 | --------------------------------------------------------------------------- SQL> alter table t modify owner null; Table altered. SQL> create index t_idx on t(status,owner); Index created. SQL> select count(*) from t where owner = 'SCOTT'; ------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 17 | 70 (2)| 00:00:01 | | 1 | SORT AGGREGATE | | 1 | 17 | | | |* 2 | INDEX FAST FULL SCAN| T_IDX | 17 | 289 | 70 (2)| 00:00:01 | -------------------------------------------------------------------------------