####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 | -------------------------------------------------------------------------------