圖 3-1 B-樹索引的內部結構

分支塊和頁塊

B-樹索引有兩種型別的塊: 用於查詢的分支塊和用於儲存值的葉塊。B-樹索引的上層分支塊包含指向下層索引塊的索引資料。在圖 3-1 中,根分支塊包含條目 0-40, 指向下一個分支級別中最左邊的塊。此分支塊包含條目如0-10 和 11-19 等。每個這些條目指向包含在該範圍內鍵值的葉塊。

葉塊包含每個被索引的資料值,和一個相應的用來定位實際行的 rowid。每個條目按 (rowid,鍵)排序。在一個葉塊內,鍵和 rowid 連結到其左右同級條目。這些葉塊本身也雙向連結在一起。如圖 3-1,最左邊的葉塊 (0-10) 連結到第二個葉塊 (11-19)

索引掃描

在索引掃描中,資料庫使用語句指定的索引列,通過遍歷索引來檢索行。資料庫掃描索引,將使用 n個 I/O 就能找到其要查詢的值,其中 n 即是 B-樹索引的高度。這是資料庫索引背後的基本原理.

如果 SQL 語句僅訪問被索引的列,那麼資料庫只需直接從索引中讀取值,而不用讀取表。如果該語句同時還需要訪問除索引列之外的列,那麼資料庫會使用 rowids 來查詢表中的行(這就是我們所說的回表)。通常,為檢索表資料,資料庫以交替方式先讀取索引塊,然後讀取相應的表塊。

完全索引掃描

在完全索引掃描中,資料庫順序讀取整個索引。如果在 SQL 語句中的謂詞(WHERE 子句) 引用了一個索引列,或者在某些情況下未不指定任何謂詞,此時可能使用完全索引掃描。完全掃描可以消除排序,因為資料本身就是基於索引鍵排過序的.

假設應用程式執行以下查詢:

SELECT department_id, last_name, salary

FROM employees

WHERE salary > 5000

ORDER BY department_id, last_name;

此外假定 department_id last_name,和 salary 是一個複合索引鍵。Oracle 資料庫會執行完全索引掃描,按順序讀取 (按部門 ID 和姓氏順序) 並基於薪金屬性進行篩選。通過這種方式,資料庫只需掃描一個小於僱員表的資料集,而不用掃描那些未包含在查詢中的列,並避免了對該資料進行排序.

快速完全索引掃描

快速完全索引掃描是一種完全索引掃描,資料庫並不按特定的順序讀取索引塊。資料庫僅訪問索引本身中的資料,而無需訪問表。

當索引包含了查詢所需的所有列,且索引鍵中至少一列具有 NOT NULL 約束時,快速完全索引掃描可以替代全表掃描.

例如,應用程式發出以下查詢,不包含 ORDER BY 子句:

SELECT last_name, salary

FROM employees;

如果姓氏和工資是一個複合索引鍵,那麼快速完全索引掃描只需讀取索引條目,就可以獲取所需的資訊:

Baida,2900,rowid

Zlotkey,10500,rowid Zlotkey,10500,rowid

Austin,7200,rowid Austin,7200,rowid

Baer,10000,rowid Baer,10000,rowid

Atkinson,2800,rowid Atkinson,2800,rowid

Austin,4800,rowid Austin,4800,rowid

索引範圍掃描是對索引的有序掃描,具有以下特點:

在條件中指定了一個或多個索引前導列。條件指定一個或多個表示式和邏輯 (布林) 運算子的組合,並返回一個值( TRUE、 FALSE,或UNKNOWN)。

一個索引鍵可能對應 0 個、1 個或更多個值。

通常,資料庫使用索引範圍掃描來訪問選擇性的資料。選擇性是查詢所選擇的資料佔總行數的百分比, 0 意味著沒有任何行,1 表示所有行。選擇性與一個(或多個)查詢謂詞相關,比如 WHERE last_name LIKE 'A%'。值越接近 0 的謂詞越具有選擇性,相反,越接近 1 的謂詞則越不具有選擇性。

例如,使用者查詢姓氏以 A 開頭的僱員。假定 last_name 列已被索引,其索引

條目如下所示:

Abel,rowid

Ande,rowid

Atkinson,rowid

Austin,rowid

Austin,rowid

Baer,rowid

資料庫可以使用範圍掃描,因為在謂詞中指定了 last_name 列,而且每個索引鍵可能對應多個 rowids。例如有兩個僱員名叫 Austin,所以有兩個rowids 都與索引鍵 Austin 相關聯.索引範圍掃描可以在兩邊都有邊界,比如部門 ID 在 10 至 40 之間的查詢,或只在一邊有界,比如部門 id 在 40 以上的查詢。為掃描索引,資料庫將在葉塊之間前後移動。例如,對於 ID 在 10 到 40 之間的掃描,將先定位到包含最低鍵值(大於或等於 10)的第一個索引葉塊。然後順著各個被連結的頁結點水平推進,直到它找到一個大於 40 的值為止。

唯一索引掃描

相對於索引範圍掃描,唯一索引掃描必須是 有 0 個 或 1 個 rowid 與索引鍵相關聯。當一個謂詞使用相等運算子引用了唯一索引鍵的所有列時,資料庫

將執行唯一掃描。只要找到了第一個記錄,唯一索引掃描就停止處理,因為不可能有第二個記錄滿足條件。

作為演示,假定使用者執行如下查詢:

SELECT *

FROM employees

WHERE employee_id = 5;

假定 employee_id 列是主鍵,並具有如下的索引條目:

1,rowid

2,rowid

4,rowid

5,rowid

6,rowid

在這種情況下,資料庫可以使用唯一索引掃描來定位 rowid,以找到 ID 為 5 的僱員

索引跳躍掃描

索引跳躍掃描使用複合索引的邏輯子索引。資料庫“跳躍地”通過單個索引,好像它在多個單獨的索引中搜索一樣。如果在複合索引前導鍵列中有少

量不同值,而在非前導鍵列中有大量不同值,此時使用跳躍掃描是有益的。

當在查詢謂詞中未指定組合索引的前導列時,資料庫可能選擇索引跳躍掃描。

SELECT * FROM sh.customers WHERE cust_email = '[email protected]';

customers 表中有一列 cust_gender,其值為 M 或 F。假定在列cust_gender 和 cust_email 上存在一個複合索引。示例 3-1 顯示了索引條目的一部分。

F,[email protected],rowid

F,[email protected],rowid

F,[email protected],rowid

F,[email protected],rowid

F,[email protected],rowid

F,[email protected],rowid

M,[email protected],rowid

M,[email protected],rowid

雖然未在 WHERE 子句中指定 cust_gender,資料庫可以使用跳躍索引掃描。

在跳躍掃描中,邏輯子索引的數目決定於前導列中的非重複值的數目。在示例 3-1 中,前導列中有兩個可能值。資料庫在邏輯上將該索引拆分為一個具

有 F 鍵的子索引和另一個具有 M 鍵的子索引。

當搜尋電子郵件為 [email protected] 的客戶的記錄時, 資料庫首先搜尋 F值的子索引,然後搜尋 M 值的子索引。從概念上講,資料庫這樣處理查詢,如下所示:

SELECT * FROM sh.customers WHERE cust_gender = 'F' AND cust_email = '[email protected]'

UNION ALL

SELECT * FROM sh.customers WHERE cust_gender = 'M' AND cust_email = '[email protected]';

索引聚簇因子用於測量相對於某個索引值(如僱員姓氏)的行順序。被索引值的行儲存得越有序,則聚簇因子越低。

如果聚簇因子較高,則在大型索引範圍掃描過程中,資料庫將執行相對較高數目的 I/O。索引條目指向隨機表塊,因此資料庫可能必須一遍又一遍地來回重讀索引所指向的同一資料塊。在一個單一葉塊中的相鄰索引條目傾向於指向同一個資料塊中的行。

查詢索引聚簇因子

SQL> SELECT INDEX_NAME, CLUSTERING_FACTOR

FROM ALL_INDEXES

WHERE INDEX_NAME IN

('EMP_NAME_IX','EMP_EMP_ID_PK');

INDEX_NAME CLUSTERING_FACTOR

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

EMP_EMP_ID_PK 19

EMP_NAME_IX 2

反向鍵索引

反向鍵索引是一種 B-樹索引,它在物理上反轉每個索引鍵的位元組,但保持列順序不變。例如,如果索引鍵是 20,,並且在一個標準的 B-樹索引中此

鍵被存為十六進位制的兩個位元組 C1 15, 那麼反向鍵索引會將其存為 15,C1。

反向鍵解決了在 B-樹索引右側的的葉塊爭用問題。在 Oracle RAC資料庫中的多個例項重複不斷地修改同一資料塊時,這個問題尤為嚴重。例如,在訂單表中,訂單的主鍵是連續的。在叢集中的一個例項新增訂單 20,而另一個例項新增訂單 21,每個例項都將它的鍵寫入索引右側的相同葉塊。

在一個反向鍵索引中,對位元組順序反轉,會將插入分散到索引中的所有葉塊。例如鍵 20 和 21,本來在一個標準鍵索引中會相鄰,現在儲存在相隔很遠的獨立的塊中。這樣,順序插入產生的 I/O 被更均勻地分佈了。

因為儲存資料時,並沒有按照鍵列排序,因此在某些情況下,反向鍵格式喪失了執行索引範圍掃描查詢的能力。例如,如果使用者發出一個訂單 id 大於20 的查詢,但資料庫不能從包含此 ID 的塊開始掃描並沿著葉塊水平推進。

升序和降序索引

對於升序索引,資料庫按升序排列的順序儲存資料。預設情況下,字元資料按每個位元組中包含的二進位制值排序, 數值資料按從小到大排序,日期資料從早到晚排序。

CREATE INDEX emp_deptid_ix ON hr.employees(department_id);

Oracle 資料庫對 hr.employees 表按 department_id 列進行排序。從 0 開始,按 department_id 列及相應的 rowid 值的升序順序載入索引。使用此索引,資料庫搜尋已排序的 department_id 值,並使用相關聯的 rowids 來定位包含所請求的 department_id 值的行。

通過在 CREATE INDEX 語句中指定 DESC 關鍵字,您可以建立一個降序索引。在這種情況下,索引在指定的一列或多列上按降序順序儲存資料。如果

在圖 3-1 中 employees.department_id 列上的索引為降序,則包含 250 的葉塊會在樹的左側,而 0 在右側。降序索引的預設搜尋順序是從最高值到最低值。當要求查詢按一些列升序而另一些列降序排序時,降序索引非常有用。假定您在 last_name 列和 department_id 列上建立一個複合索引,如

下所示:

CREATE INDEX emp_name_dpt_ix ON hr.employees(last_name ASC, department_id DESC);

一個對 hr.employees 使用者查詢,要求按姓氏以升序順序 (A 到 Z) 而部門id 以降序 (從高到低)排序,則資料庫可以使用此索引檢索資料並避免額外的排序步驟。

鍵壓縮

Oracle 資料庫可以使用鍵壓縮來壓縮 B-樹索引或索引組織表中的主鍵列值的部分。鍵壓縮可以大大減少索引所使用的空間。

一般地,索引鍵包含兩個片斷,一個分組片斷和一個唯一片斷。鍵壓縮將索引鍵分成兩部分,即作為分組片斷的字首條目,和作為唯一或幾乎唯一片斷的字尾條目。資料庫通過在一個索引塊中的多個字尾條目之間共享字首條目來實現壓縮。

如果某個鍵未被定義為具有唯一片斷,那麼資料庫會通過將一個 rowid 附加到分組片斷來提供唯一片斷

CREATE INDEX orders_mod_stat_ix ON orders ( order_mode, order_status );

在 order_mode 和 order_status 列中有許多重複值出現。一個索引塊中的條目可能如示例 3-3 中所示。

示例 3-3 訂單表中的索引條目

online,0,AAAPvCAAFAAAAFaAAa

online,0,AAAPvCAAFAAAAFaAAg

online,0,AAAPvCAAFAAAAFaAAl

online,2,AAAPvCAAFAAAAFaAAm

online,3,AAAPvCAAFAAAAFaAAq

online,3,AAAPvCAAFAAAAFaAAt

示例 3-3 中,鍵字首將包括 order_mode 和 order_status 的串聯值。如果此索引按預設鍵壓縮建立,那麼重複鍵字首(如 online,0 和online,2)將會被壓縮。從概念上講,資料庫按如下示例中所示實現壓縮:

online,0

AAAPvCAAFAAAAFaAAa

AAAPvCAAFAAAAFaAAg

AAAPvCAAFAAAAFaAAl

online,2

AAAPvCAAFAAAAFaAAm

online,3

AAAPvCAAFAAAAFaAAq

AAAPvCAAFAAAAFaAAt

或者,建立一個壓縮索引時,您也可以指定字首長度。例如,如果指定了字首長度為 1 ,那麼字首將會是 order_mode,而後綴將會是 order_status,rowid。對於示例 3-3 中的數值,索引會提取重複出現的 online 作為字首,如下所示:

online

0,AAAPvCAAFAAAAFaAAa

0,AAAPvCAAFAAAAFaAAg

0,AAAPvCAAFAAAAFaAAl

2,AAAPvCAAFAAAAFaAAm

3,AAAPvCAAFAAAAFaAAq

3,AAAPvCAAFAAAAFaAAt

對索引中的每個葉塊,一個特定字首最多被儲存一次。只有在 B-樹索引的葉塊中的鍵會被壓縮。在分支塊中的鍵字尾可以被截斷,但不會被壓縮。