表的訪問方法

1.全掃描訪問方法

當對一個對象進行全掃描時,與該對象相關的所有數據塊都必須取出並進行處理,以確定塊中所有包含的數據行是否是你查詢的所需要的。請記住Oracle必須將整個數據塊讀取到內存以取得這個塊所存儲的數據行的數據,因此,當發生全掃描時,實際上優化器(還有你)需要考慮兩件事:必須讀取多少個數據塊以及每個數據塊中有多少數據將被捨棄。此時此刻要理解的是,確定全掃描是否是正確選擇並不僅僅基於查詢將會返回多少行。

1.1 如何選擇全掃描操作

它不僅僅是與數據行有關,還與數據塊丟棄有關,將所有這些方面的信息集合起來,就會得出全表掃描是否有意義的結論,即使返回的數據行百分比可能很小。另一方面,即使返回的數據行白分比很高也有可能不應該選擇全表掃描。

SQL> create table t1 as select trunc((rownum-1)/100) id,rpad(rownum,100)t_pad from dba_source where rownum<=10000;
Table created.
SQL> create index t1_idx1 on t1(id);
Index created.
SQL> exec dbms_stats.gather_table_stats(user,'t1',method_opt=>'FOR ALL COLUMNS SIZE 1',cascade=>true);
PL/SQL procedure successfully completed.
SQL> create table t2 as select mod(rownum,100) id,rpad(rownum,100) t_pad from dba_source where rownum<=10000;
Table created.
SQL> create index t2_idx1 on t2(id);
Index created.
SQL> exec dbms_stats.gather_table_stats(user,'t2',method_opt=>'FOR ALL COLUMNS SIZE 1',cascade=>true);
PL/SQL procedure successfully completed.

select count(*) from t1 where id=1
Plan hash value: 3695297570
-----------------------------------------------------------------------------
| Id  | Operation  | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |    |   |    | 1 (100)|    |
|   1 |  SORT AGGREGATE   |    | 1 |  3 ||    |
|*  2 |   INDEX RANGE SCAN| T1_IDX1 | 100 |300 |  1   (0)| 00:00:01 |

-----------------------------------------------------------------------------
SQL> select count(*) from t2 where id=1;

 COUNT(*)
----------
       100

儘管返回結果相同,但是第一走索引,第二全表掃描,因數據分佈情況不一樣。

1.2 全掃描與捨棄

請記住,全掃描是否爲高效的選擇取決於需要訪問的數據塊個數已經最終的結果集行數,另外數據存儲方式在決策過程中扮演重要的角色。此外,全表掃描是否高效選擇的另一個關鍵因素是捨棄。捨棄的行是通過一個篩選謂語來進行驗證,被證明是不符合篩選條件後從最終結果集中剔除的數據行。

SQL> select table_name,num_rows,blocks from user_tables where table_name='T2';
TABLE_NAME NUM_ROWS     BLOCKS
------------------------------ ---------- ----------
T2    10000 152
1.3 全掃描與多塊讀取

關於全掃描,你還需要知道的是數據塊的如何讀取的,全運算掃描將會進行多塊讀取,也就是說一個單獨的IO調用講會請求多個塊而不是僅僅一個。所請求的數據塊數目是可變的,實際上可以是從一個到db_file_multiblock_read_count參數所指定的數目範圍之間的任意個。例如,如果這個參數設置爲16,而表中有160個塊,可以通過10次調用就可以獲取所有的數據塊。

oracle不得讀取超過一定邊界的範圍的數據塊,在這種情況下,oracle將會在第一次調用中讀取到邊界範圍的數據塊,然後發起另一次調用來讀取剩餘的數據塊。

1.4 全掃描與高水位線

oracle將最多讀取到位於表中高水位線的數據塊,高水位線標出了表中最後一塊有數據寫入的數據塊,爲了保持技術上的正確性,實際上應該被稱爲“低”高水位線。

當數據行插入一張表中的時候,就會爲其分配數據塊並將數據行放到其中。

delete數據後,高水位線不會下降,當運行全表掃描的時候,仍然會掃描高水位線的所有數據塊,truncate可以下降高水位線。

2 .索引掃描訪問方法

默認的索引類型是B-樹索引,索引建立在表中的一個或多個列或者列的表達式,將列值和行編號(rowid)一起存儲。索引中還保存着一些其他信息,我們只需要關注列值和行編號,行編號是一個用來唯一標記表中的僞列,它是物理表中行數據的內部地址,包含兩個地址,其一指向數據表中包含該行的塊所存放數據文件的地址,另一個可以直接定位到數據行自身的這一行的再數據塊中的地址。

行編號指向一個特定的準確位置,因此當使用索引來訪問數據行時,就會對謂語所提供的訪問標準來進行匹配,然後使用行編號來訪問指定的文件/數據塊/數據行,通過索引掃描來進行的數據塊訪問是通過單塊讀取來完成的,一旦讀取了索引條目,就只會取出行編號所指定的那一塊數據,在它被取出以後,就僅有行編號所指定的行被訪問。

也就是說對於通過索引掃描所獲取的每一行,都至少需要訪問兩個數據塊,至少是一個索引塊和一個數據塊,

2.1 索引掃描類型

索引掃描有好幾種不同的類型,但每種類型都有的共同點就是他們必須遍歷索引結構以訪問匹配所搜索的葉子索引塊。首先,索引的根塊將 通過一次單塊讀取來訪問,接下來就是讀取分支塊。最後,讀取了包含所需的起始索引條目的第一葉子索引塊。如果索引的高度是4,問了得到所需的葉子塊,將會對4個單獨的塊進行讀取。此時,就會讀取葉子塊中第一個匹配的索引值得行編號,並利用這個行編號來進行一次獨立的塊讀取以獲取整個行數據所存儲的表數據塊。因此,在這個例子中,使用索引從表中取出一行,oracle必須讀取5個塊:4個索引塊和1個表數據塊。

索引掃描類型:索引範圍掃描,索引唯一掃描,索引全掃描,索引跳躍掃描以及索引快速全掃描。索引的快速全掃描實際上更像是進行一次全表掃描,但由於它掃描的是索引結構

一個非常重要的索引統計信息——聚簇因子(clustering factor)。索引的聚簇因子統計信息幫助優化器生成使用索引的成本信息,並且是表中建立了索引的數據排序優良度的一個度量值,索引的聚簇因子向優化器表明了具有同樣的索引值的數據行是不是存放同一個或連續的一系列數據塊中,或者數據行是否被分散存放表的多個數據塊中。

SELECT t.table_name || '.' || i.index_name idx_name,
       i.clustering_factor,
       t.blocks,
       t.num_rows
  FROM user_indexes i, user_tables t
 WHERE i.table_name = t.table_name
   AND t.table_name IN ('T1', 'T2')
  8   ORDER BY t.table_name, i.index_name;

IDX_NAME      CLUSTERING_FACTOR     BLOCKS   NUM_ROWS
------------------------------------------------------------- ----------------- ---------- ----------
T1.T1_IDX1    154         15210000
T2.T2_IDX1  10000         15210000

儘管大多數時候這種聚簇因子計算上的偏差不會引起足夠大的差別以至於導致優化器錯誤地估計索引成本而放棄索引,單的確可能出現這樣的情況。如果優化器沒有選擇你所希望的使用的索引,它還有可能選擇另個能滿足謂語的類似數據列上的索引。在這樣的情況下,你就需要仔細分析所創建的索引,看看是否存在一種方法可以將幾個索引合併爲一個複合索引。不要錯誤地認爲可以通過重建索引來“修正”聚簇因子。聚簇因子和表中數據不是索引有關,因此,重建索引對它沒有任何影響。

2.2 索引唯一掃描

當謂語中包含使用unique或primary key索引的列作爲調節的時候會選用索引唯一掃描。這種類型的索引能夠保證對於某個特定的值只返回一行數據。在這種情況下,索引結構將會被從根到葉子進行遍歷直到某個條目,取出其編號。然後使用這個行編號來訪問包含這一行數據的數據塊。計劃中table access by index rowid步驟表明了對於表數據塊的訪問。

SQL> set autotrace traceonly explain
SQL> select * from 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 |
---------------------------------------------------------------------------------------------

2.3 索引範圍掃描

當謂語中包含將會返回一定範圍數據的條件時會選用索引範圍掃描。索引可以時唯一或者不唯一的,因爲是由該條件來確定是否返回多個數據行。所指定的條件可以使用<.>,LIKE,BETWEEN甚至是=運算符,爲了能夠選用索引範圍掃描,範圍需要相當仔細地進行選擇,範圍越大,就越有可能會選用全掃描運算來代替它。

SQL> select * from employees where department_id=60;
Execution Plan
----------------------------------------------------------
Plan hash value: 2056577954
-------------------------------------------------------------------------------------------------
| Id  | Operation    | Name| Rows | Bytes | Cost (%CPU)| Time|
-------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT    ||     5 |   345 |     2   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| EMPLOYEES |     5 |   345 |     2   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN    | EMP_DEPARTMENT_IX |     5 ||     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------

索引範圍掃描將會從根數據塊開始到第一個包含符合特定條件的條目所在的葉子數據塊來遍歷索引結構。再從那一點開始,從索引條目中取出一個行編號然後取出相應的表數據塊(通過索引行編號訪問數據表),在第一行被取出之後,之前的葉子索引塊將再一次被訪問並讀取下一個索引條目來獲取下一個行編號。這種索引葉子塊和表數據塊之間的反覆來回將會不斷持續直到所有匹配的索引條目被讀出。因此,所需訪問數據塊的次數將包括索引的分支塊數(可以通過索引的blevel統計信息得出)加上符合條件的索引條目數乘以2.必須要乘以2是因爲每取出表中的一行需要訪首先訪問的索引葉子塊取出行編號然後通過該行編號訪問數據塊。

索引範圍掃描的精妙之處在於它能夠使用一個升序排列的索引來返回降序排列的數據行。

SQL> select * from employees where department_id in(90,100) order by department_id desc;
Execution Plan
----------------------------------------------------------
Plan hash value: 3707994525
---------------------------------------------------------------------------------------------------
| Id  | Operation      | Name | Rows  | Bytes | Cost (%CPU)| Time  |
---------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |  | 9 |   621 |2   (0)| 00:00:01 |
|   1 |  INLIST ITERATOR      |  |  |  |       |  |
|   2 |   TABLE ACCESS BY INDEX ROWID | EMPLOYEES  | 9 |   621 |2   (0)| 00:00:01 |
|*  3 |    INDEX RANGE SCAN DESCENDING| EMP_DEPARTMENT_IX |9 |  | 1   (0)| 00:00:01 |

---------------------------------------------------------------------------------------------------

2.4 索引全掃描

在以下情況選擇索引全掃描:當沒有謂語但是所獲取的列表可以通過其中一列的索引來獲得,當謂語胡總包含一個位於索引非引導列上的條件,或者數據可以通過一個排過序的索引來獲取並且會省去單獨排序的步驟。

SQL> select email from employees;
Execution Plan
----------------------------------------------------------
Plan hash value: 2196514524
---------------------------------------------------------------------------------
| Id  | Operation | Name | Rows | Bytes | Cost (%CPU)| Time|
---------------------------------------------------------------------------------
|   0 | SELECT STATEMENT | |   107 |   856 |     1   (0)| 00:00:01 |
|   1 |  INDEX FULL SCAN | EMP_EMAIL_UK |   107 |   856 |     1   (0)| 00:00:01 |
---------------------------------------------------------------------------------

SQL> select * from employees order by employee_id;
Execution Plan
----------------------------------------------------------
Plan hash value: 2186312383
---------------------------------------------------------------------------------------------
| Id  | Operation    | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |   | 107 |  7383 | 3   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| EMPLOYEES     |107 |  7383 |  3   (0)| 00:00:01 |
|   2 |   INDEX FULL SCAN    | EMP_EMP_ID_PK |107 |    | 1   (0)| 00:00:01 |
---------------------------------------------------------------------------------------------

索引全掃描運算將會掃描索引結構中每一個葉子塊,讀取每一個條目的行編號,並讀取數據行。每個葉子塊都有被訪問,這個通常比全表掃描效率要高因爲每一個索引塊要比數據塊包含的更多條目,從而總的需要訪問的塊數也就相應較少。

SQL> select min(department_id) from employees;
Execution Plan
----------------------------------------------------------
Plan hash value: 613773769
------------------------------------------------------------------------------------------------
| 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 |
------------------------------------------------------------------------------------------------

SELECT (SELECT MIN(department_id) FROM hr.employees) min_id,
       (SELECT MAX(department_id) FROM employees) max_id
  3    FROM dual;
Execution Plan
----------------------------------------------------------
Plan hash value: 2189307159
------------------------------------------------------------------------------------------------
| Id  | Operation   | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     1 |       |     2 (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 |
|   3 |  SORT AGGREGATE   |      |     1 |     3 |    |      |
|   4 |   INDEX FULL SCAN (MIN/MAX)| EMP_DEPARTMENT_IX |     1 |     3 |     1(0)| 00:00:01 |
|   5 |  FAST DUAL   |      |     1 |       |     2 (0)| 00:00:01 |
------------------------------------------------------------------------------------------------

在以上情況,當使用索引來快速獲取最小值時,這個最小值將會是第一個索引葉子塊中第一個條目;當獲取最大值時,將會是最後一個索引葉子的最後一個條目。這些特別的例子中索引全掃描並不是真正的全掃描——僅僅是對根塊,一個或多個分支塊以及第一個和最後一個葉子塊的掃描。也就是說找到最大/最小值在所需訪問的塊數目方面耗費的成本是很低的,速度是最快的,

2.5 索引跳躍掃描

當謂語中包含位於索引中非引導列上的條件,並且引導列的值是唯一的時候會選擇索引跳躍掃描。

2.6 索引快速全掃描

索引快速全掃描更像全表掃描而不像其他類型的索引掃描,當選用索引快速全掃描時,所有索引塊都將通過多塊讀取來進行讀取。這種類型的索引掃描是用來在查詢列表中所有字段都包含在索引中並且索引中至少有一列具有非空約束時替代全表掃描的。在這種情況下,數據通過索引來進行訪問而不必訪問表的數據塊。與其他索引掃描類型不同,索引快速掃描並不能用來避免排序,因爲數據塊時通過無序的多表讀取來讀取的。

SQL> alter table hr.employees modify(email null);
Table altered.
SQL> set autotrace traceonly explain
SQL> select email from hr.employees;
Execution Plan
----------------------------------------------------------
Plan hash value: 1445457117
-------------------------------------------------------------------------------
| Id  | Operation  | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |   107 |   856 |     3(0)| 00:00:01 |
|   1 |  TABLE ACCESS FULL| EMPLOYEES |   107 |   856 |     3(0)| 00:00:01 |
-------------------------------------------------------------------------------

以上例子說明了索引快速全掃描運算爲了被選擇是如何依賴於非空約束的,如果沒有這個約束,將會選擇全表掃描運算。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章