Oracle 優化器_訪問資料的方法_單表
Oracle 在選擇執行計劃的時候,優化器要決定用什麼方法去訪問儲存在資料檔案中的資料。我們從資料檔案中查詢到相關記錄,有兩種方法可以實現:1.直接訪問表記錄所在位置。2.訪問索引,拿到索引中對應的rowid,然後根據rowid 去表中獲取相應的資料。(有些情況,不需要再去表中取資料就可以得到相應的結果,那麼就會直接返回)。
訪問表的方法
全表掃描
全表掃描,Oracle 在取資料庫資料的時候,從該表在硬碟上的第一個資料塊開始,掃描到該表的最高水位線所在的資料塊。在讀的時候,會使用多塊讀的技術,將全表掃描一遍表資料,然後將不滿足的資料剔除掉,返回需要的資料。Oracle 全表掃描的速度取決於最高水位線的大小。當表中的高水位線越大,需要消耗的資源(主要是I/O資源)越多。這樣一來,耗費的時間也會增加。
高水位:在Oracle中,表是屬於表空間的,如果建表的時候,沒有設定表空間,那麼就會將當前使用者的預設空間作為表格所在的表空間。如果一直往表中插入資料,分配給表的空間用完了,高水位線就會向上移動。如果用delete語句刪除資料,水位線也不回降下來。這就會導致,有的表雖然只有幾條資料,但是全表掃描就會很消耗效能。所以在進行大量的delete操作的時候,需要執行降水位的操作。
ROWID 掃描
rowId 類似於指標的概念。rowid和資料塊中的行資料是一一對應的。我們知道某一行對應的rowid後,可以直接通過rowid去直接訪問相應資料對應的資料行。Oracle 使用row取資料有兩種:1、直接用rowId從取得相應資料。2、根據索引獲得rowId,然後取資料。
我們獲取rowid的方法很簡單,在每行記錄中,都有一個Oracle內建的偽列rowId 直接在查詢的時候去獲取就可以了(注:這裡需要將rowId 進行重新命名,不然無法返回,不知道是不是筆者個人原因還是都需要這麼寫)。以emp表為例:
SELECT ENAME ,EMPNO, rowId dataRowId FROM EMP;
查出來的結果如下:
SMITH 7368 AAAtkkAAGAABqYkAAA SMITH 7369 AAAtkkAAGAABqYkAAB ALLEN 7499 AAAtkkAAGAABqYkAAC WARD 7521 AAAtkkAAGAABqYkAAD JONES 7566 AAAtkkAAGAABqYkAAE MARTIN 7654 AAAtkkAAGAABqYkAAF BLAKE 7698 AAAtkkAAGAABqYkAAG CLARK 7782 AAAtkkAAGAABqYkAAH SCOTT 7788 AAAtkkAAGAABqYkAAI KING 7839 AAAtkkAAGAABqYkAAJ TURNER 7844 AAAtkkAAGAABqYkAAK ADAMS 7876 AAAtkkAAGAABqYkAAL JAMES 7900 AAAtkkAAGAABqYkAAM FORD 7902 AAAtkkAAGAABqYkAAN MILLER 7934 AAAtkkAAGAABqYkAAO
這裡查出的rowId可以直接作為where條件去進行查詢,SQL如下:
SELECT ENAME ,EMPNO, rowId dataRowId FROM EMP where rowId='AAAtkkAAGAABqYkAAA';
執行結果:
這條SQL的執行計劃如下:
Plan hash value: 1116584662 ----------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ----------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 22 | 1 (0)| 00:00:01 | | 1 | TABLE ACCESS BY USER ROWID| EMP | 1 | 22 | 1 (0)| 00:00:01 | ----------------------------------------------------------------------------------- Note -----
從Id為1的這一行可以看出,執行計劃走的是 BY USER ROWID 這個執行計劃。我們對比下通過主鍵EMPNO去查詢得到的執行計劃:
執行SQL如下:
SELECT ENAME ,EMPNO, rowId dataRowId FROM EMP where EMPNO='7368';
執行結果跟通過rowId執行得到的執行結果一致:
其執行計劃如下:
Plan hash value: 2137789089 --------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 8168 | 16336 | 29 (0)| 00:00:01 | | 1 | COLLECTION ITERATOR PICKLER FETCH| DISPLAY | 8168 | 16336 | 29 (0)| 00:00:01 | --------------------------------------------------------------------------------------------- Note -----
可以看出,通過rowId得到的結果比使用主鍵進行查詢的消耗要小的多,因為主鍵是先通過主鍵索引找到rowId,然後進行資料的提取操作,而rowId則是直接從資料檔案中提取資料。
訪問索引的方法
索引結構(B樹索引)
在說通過索引掃描資料之前,先介紹下什麼是索引。Oracle資料庫中用的最多的是B樹索引。B樹索引的結構如下圖所示:
索引包含兩個部分,一部分是索引分支塊,另外一部分是葉子塊。在資料根據索引進行掃描的時候,可以根據資料的內容,計算得出一個索引的值。然後根據索引值,得到響應的rowId,然後根據rowid,去資料檔案取出相應的資料。Oracle 通過索引訪問表裡的記錄的效率並不會隨著相關表的資料量的遞增而顯著降低,所以索引訪問資料的時間是基本穩定可控的。
索引唯一性掃描(INDEX UNIQUE SCAN)
索引唯一性掃描,是針對唯一性索引(unique scan)進行的掃描。當它的where條件是等於號的時候,掃描結果至多會返回一條資料記錄。例如:sql語句:
select * from emp where EMPNO = 7368
執行計劃:
Plan hash value: 2949544139 -------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 37 | 1 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID| EMP | 1 | 37 | 1 (0)| 00:00:01 | |* 2 | INDEX UNIQUE SCAN | PK_EMP | 1 | | 0 (0)| 00:00:01 | -------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("EMPNO"=7368) Note
索引範圍掃描(INDEX RANGE SCAN)
範圍索引掃描,使用於所有型別的B樹索引,當掃描物件是唯一性索引時,目標的SQL條件一定是範圍條件,例如 where 條件為between、<、> 等。
例如,SQL語句為:
select * from emp where EMPNO > 7933
執行計劃為:
Plan hash value: 2787773736 ---------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ---------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 37 | 2 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID BATCHED| EMP | 1 | 37 | 2 (0)| 00:00:01 | |* 2 | INDEX RANGE SCAN | PK_EMP | 1 | | 1 (0)| 00:00:01 | ---------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("EMPNO">7933) Note
通過對比範圍索引和唯一索引可以看出,即使使用同樣的索引,範圍索引也比唯一索引消耗更多的CPU,因為範圍索引至少要多一次邏輯讀。
索引全掃描(INDEX FULL SCAN)
索引在做全掃描的時候,要求索引不能為空。不然會漏掉null 的欄位。索引全掃描在預設情況下,直接從第一個葉子節點,通過葉子節點之間相互的連結串列指標進行跳轉。既能保證資料有序,又避免了對索引真正值的排序操作。
sql語句:
SELECT EMPNO FROM EMP
執行計劃 :
Plan hash value: 179099197 --------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 16 | 64 | 1 (0)| 00:00:01 | | 1 | INDEX FULL SCAN | PK_EMP | 16 | 64 | 1 (0)| 00:00:01 | --------------------------------------------------------------------------- Note
索引快速全掃描(INDEX FAST FULL SCAN)
索引快速掃描和索引全掃描類似,與之相比有如下區別:
1.快速全掃描只適用於CBO。
2.索引快速全掃描可以使用多塊讀,也可以併發執行。
3.索引快速全掃描結果不一定有序。
索引跳躍式掃描(INDEX SKIP SCAN)
索引跳躍式掃描適用於組合索引。適用場景舉例:當前索引有兩個列,為:C1,C2。當我們的SQL語句where條件中,沒有對C1進行篩選,而是對C2進行了篩選。那麼有時候就會出現使用跳躍式索引掃描的情況。Oracle 中索引跳躍掃描適用於前導列可選擇性較差,後續列的可選擇性又非常好的場景。因為前導列包含的distinct值越少,跳躍次數也就越少,索引效率也就越