介紹#
索引(在 MySQL 中也稱為 “鍵”)是存儲引擎用來快速查找行的數據結構。它們還具有幾個其他有益的特性,我們將在本章中探討。
因此,它們並不是標準化的:在每個引擎中,索引的工作方式略有不同,並且並非所有引擎都支持所有類型的索引。即使多個引擎支持相同的索引類型,它們在內部的實現方式也可能不同。
在 MySQL 中,存儲引擎使用索引,就像您在書籍索引中用來查找包含特定術語的頁面一樣。它在索引的數據結構中搜索一個值。當找到匹配時,它可以找到包含該匹配的行。假設您運行以下查詢:
SELECT first_name FROM sakila.actor WHERE actor_id = 5;
[!info] 索引包含來自表中一個或多個列的值。如果您對多個列進行索引,列的順序非常重要,因為 MySQL 只能在索引的最左前綴上高效搜索。
在兩列上創建索引與創建兩個單獨的單列索引並不相同,正如您將看到的那樣。
索引類型#
有許多類型的索引,每種類型都旨在在不同的目的下表現良好。我們將涵蓋:
-
B - 樹索引
-
哈希索引
-
位圖索引
[!info] 索引是在存儲引擎層中實現的,而不是在服務器層中。
MySQL 在 CREATE TABLE
和其他語句中默認使用 B - 樹索引。然而,存儲引擎可能在內部使用不同的存儲結構。
存儲引擎的性能可能會受到 B - 樹索引使用方式的影響。例如,MyISAM 使用一種前綴壓縮方法來減少索引大小,而 InnoDB 保持索引值不壓縮。此外,MyISAM 索引根據其物理存儲位置識別被索引的行,而 InnoDB 則使用其主鍵值(聚集鍵)來引用它們,這將在後面詳細說明。每種方法都有其優缺點。
B - 樹的一般思想是所有值按順序存儲,每個葉頁與根的距離相同。
此圖顯示了 InnoDB 存儲引擎使用的 B - 樹索引的抽象表示。

葉頁的特點在於它們包含指向被索引數據的指針,而不是指向其他頁的指針。不同存儲引擎之間的 “指針” 類型各不相同。
因為 B - 樹按順序存儲索引列,所以它們對於搜索數據範圍非常有用。
讓我們舉個例子:
CREATE TABLE People (
last_name varchar(50) not null,
first_name varchar(50) not null,
dob date not null,
gender enum('m', 'f')not null,
key(last_name, first_name, dob)
);
這裡的索引將包含表中每行的 last_name、first_name 和 dob 列的值。以下是索引如何排列它所存儲的數據:

請注意,索引根據在 CREATE TABLE 語句中給出的列的順序對值進行排序。
知道何時使用 B - 樹以及何時不使用 B - 樹是非常重要的!
有些類型的查詢可以使用 B - 樹索引,這些查詢可以用於 查找:
-
完整鍵值
-
鍵範圍
-
鍵前綴
[!danger] 只有當查找使用索引的最左前綴時,它們才有用。
考慮前面的例子,讓我們看看索引何時會被使用,何時不會!
匹配完整值鍵 為索引中的所有列指定值。
SELECT first_name FROM People WHERE last_name = 'Allen' AND first_name = 'Cuba' AND dob = '1960-01-01'
匹配最左前綴 查找所有姓 Allen 的人,這將僅使用索引中的第一列。
SELECT first_name FROM People WHERE last_name = 'Allen'
匹配列前綴 匹配列值的第一部分,例如,查找所有姓氏以 J 開頭的人。
SELECT first_name FROM People WHERE last_name LIKE 'J%'
匹配值範圍 您可以找到姓氏在 Allen 和 Meska 之間的人。
SELECT first_name FROM People WHERE last_name BETWEEN 'Allen' AND 'Meska';
精確匹配一部分並在另一部分上匹配範圍 此索引可以幫助您找到所有姓 Allen 且名字以字母 K 開頭的人(如 Kim、Karl 等)。這是對 last_name 的精確匹配,對 first_name 的範圍查詢。
SELECT first_name FROM People WHERE last_name = 'Allen' AND first_name LIKE 'K%'
匹配僅索引查詢 B - 樹索引通常可以支持僅索引查詢,即僅訪問索引而不訪問行存儲的查詢。我們稍後在 “覆蓋索引” 中討論此優化。
[!info] 使用 B - 樹的另一個重要好處是以排序順序查找值以及查找,因為 B - 樹的節點是排序的。
因此,B - 樹索引對於 ORDER BY
和 GROUP BY
子句將非常有幫助。
使用 B - 樹索引的缺點和限制#
如果查找不從被索引列的最左側 開始,則它們將無法使用。
SELECT first_name FROM People WHERE first_name = 'Yousef';
上述查詢將不會使用索引。此外,您無法找到姓氏 以特定字母結尾 的人,例如,以下查詢將不會使用索引。
SELECT first_name FROM People WHERE last_name LIKE '%J';
存儲引擎無法優化對第一個範圍條件右側的任何列的訪問。例如,如果您的查詢是:
WHERE last_name="Smith" AND first_name LIKE 'J%' AND dob='1976-12-23'
索引訪問將僅使用索引中的前兩列,因為 LIKE 是一個範圍條件(服務器可以將其餘列用於其他目的)。對於具有有限值數量的列,您通常可以通過指定相等條件而不是範圍條件來解決此問題。我們在本章後面的索引案例研究中將詳細展示這些示例。
這些限制中的一些並不是 B - 樹索引固有的,而是 MySQL 查詢優化器和存儲引擎使用索引的結果。其中一些可能在未來被移除。
正如您現在所理解的,列順序的重要性不容小覷,因為這些限制都與之交織在一起。為了實現最佳性能,可能需要生成具有相同列但不同順序的索引,以滿足您的查詢。
“內聯查詢” 是指所需信息存在於索引本身的情況。例如,在查詢 “EXPLAIN ANALYZE SELECT id from employees where id = 1;” 中,其中 “id” 是主鍵(btree),則 “Heap Fetches” 值將為 0。相比之下,在查詢 “SELECT name from employees where id = 2”(其中 “name” 沒有索引)中,引擎將首先使用 “id”(在索引中)訪問頁面,該頁面包含 “name”(存儲在磁碟上,需要另一個讀取)所需的信息。如果上述查詢重複,則由於緩存,它將花費更少的時間。讓我們檢查另一個查詢。
SELECT id from employee where name = 'Zsh';
這將導致順序掃描,耗費大量時間。[全表掃描]。儘管 MySQL 以某種方式智能地應用工作線程,因此可以並行進行順序掃描。如果我們現在對 name 創建索引:
CREATE INDEX employee_idx on employees(name)
$ 位圖索引已創建。現在讓我們搜索:
SELECT id,name FROM employees Where name = 'Yousef'; (將使用索引)
SELECT id,name FROM employees WHERE name like '%You%' (將不使用索引) #
因為實際上這個表達式不是單一值,我們有很多可能性。
索引的好處#
高性能的索引策略#
由於各種優化技術和專門行為的存在,有許多方法可以有效地選擇和實施索引。獲得判斷何時使用哪種方法的能力,以及評估您決策的性能後果,是一種需要時間來發展的能力。
在本節中,我們將涵蓋以下策略:
-
隔離列
-
前綴索引和索引選擇性
-
多列索引
-
選擇良好的列順序
-
聚集索引和非聚集索引
-
覆蓋索引
隔離列#
我們經常看到會破壞索引或阻止 MySQL 使用可用索引的查詢。MySQL 通常無法在列上使用索引,除非這些列在查詢中是隔離的。“隔離” 列意味著它不應該是表達式的一部分或在查詢中的函數內。例如,這裡有一個無法在 actor_id 上使用索引的查詢:
SELECT actor_id FROM sakila.actor WHERE actor_id + 1 = 5
人類可以輕鬆地看到 WHERE 子句等同於 actor_id = 4,但 MySQL 無法解決 actor_id 的方程式。這取決於您來做到這一點。您應該養成簡化 WHERE 條件的習慣,以便被索引的列單獨位於比較運算符的一側。
前綴索引和索引選擇性#
有時,您可能需要對冗長的字符列進行索引,這可能導致大型且緩慢的索引。一種方法是創建模擬哈希索引,如本章前面所示,儘管這可能並不總是足夠。
在這種情況下可以做什麼?一種解決方案是僅對列的前幾個字符進行索引,而不是整個值。這減少了索引大小,但也降低了其選擇性。索引選擇性是不同索引值的數量(基數)與表中總行數(#T)的比率,範圍從 1/#T 到 1。高選擇性的索引是理想的,因為它使 MySQL 在搜索匹配時能夠過濾掉更多行。唯一索引的選擇性為 1,這是最佳可能值。
列的前綴通常足夠具有選擇性以實現良好的性能。如果您正在對 BLOB 或 TEXT 列或非常長的 VARCHAR 列進行索引,則必須定義前綴索引,因為 MySQL 禁止對其進行全長索引。挑戰在於選擇一個足夠長的前綴,以提供良好的選擇性,但又足夠短以節省空間。
前綴應足夠長,以使索引幾乎與整個列索引一樣有用。換句話說,前綴的基數應接近整個列的基數。
要確定良好的前綴長度,找出最常見的值並將該列表與最常見的前綴列表進行比較。
假設我們有城市的示例數據集,讓我們找出最常見的城市。
SELECT COUNT(*) AS cnt, city
FROM sakila.city_demo GROUP BY ORDER BY cnt DESC LIMIT 10
請注意,每個值大約有 45 到 65 次出現。現在我們找出最常見的城市名稱前綴,從三個字母的前綴開始:
cnt | city | |
---|---|---|
65 | 倫敦 | |
49 | 廣島 | |
48 | 特博克薩里 | |
48 | 帕克雷特 | |
48 | 雅溫得 | |
47 | 特拉維夫 - 雅法 | |
47 | 希莫加 | |
45 | 卡布亞奧 | |
45 | 卡亞奧 | |
45 | 比斯利 |
SELECT COUNT(*) AS cnt, LEFT(city, 3) AS pref -> FROM sakila.city_demo GROUP BY pref ORDER BY cnt DESC LIMIT 10;
cnt | pref |
---|---|
483 | 聖 |
195 | 查 |
177 | 譚 |
167 | 蘇 |
163 | al- |
163 | 薩爾 |
146 | 希 |
136 | 哈爾 |
130 | 瓦爾 |
129 | 巴特 |
還有許多其他前綴的出現次數,因此唯一的前綴比唯一的全長城市名稱要少得多。這個想法是增加前綴長度,直到前綴變得幾乎與整個列的選擇性一樣。稍微實驗一下,7 是一個不錯的值 | |
cnt | pref |
--- | ------- |
70 | 聖地亞哥 |
68 | 聖費爾 |
65 | 倫敦 |
61 | 瓦萊 |
49 | 廣島 |
48 | 特博克薩 |
48 | 帕克雷 |
48 | 雅溫得 |
47 | 特拉維夫 |
47 | 希莫加 |
計算良好的前綴長度的另一種方法是通過計算整個列的選擇性,並試圖使前綴的選擇性接近該值。以下是如何找到整個列的選擇性:
通過計算不同值的總數與表中值的數量的比率。
SELECT COUNT(DISTINCT city)/COUNT(*) FROM sakila.city_demo;
如果我們的目標是接近 0.031 的選擇性,則前綴的選擇性將大致相同(不過這裡有一個警告)。可以在一個查詢中評估許多不同的長度,這在非常大的表上非常有用。以下是如何在一個查詢中找到幾個前綴長度的選擇性:
SELECT COUNT(DISTINCT LEFT(city, 3))/COUNT(*) AS sel3,
COUNT(DISTINCT LEFT(city, 4))/COUNT(*) AS sel4,
COUNT(DISTINCT LEFT(city, 5))/COUNT(*) AS sel5,
COUNT(DISTINCT LEFT(city, 6))/COUNT(*) AS sel6,
COUNT(DISTINCT LEFT(city, 7))/COUNT(*) AS sel7
FROM sakila.city_demo;
這個查詢顯示,增加前綴長度會導致隨著接近七個字符而不斷減少的改進。僅查看平均選擇性並不是一個好主意。警告是,最壞情況的選擇性也很重要。平均選擇性可能會讓您認為四個或五個字符的前綴就足夠了,但如果您的數據非常不均勻,那可能是一個陷阱。如果您查看最常見的城市名稱前綴的出現次數,使用四個字符的值,您將清楚地看到不均勻性:
SELECT COUNT(*) AS cnt, LEFT(city, 4) AS pref
FROM sakila.city_demo GROUP BY pref ORDER BY cnt DESC LIMIT 5;
cnt | pref |
---|---|
205 | 聖 |
200 | 聖 |
135 | 蘇 |
104 | 查 |
91 | 圖爾 |
使用四個字符時,最常見的前綴出現的頻率比最常見的全長值要高得多。也就是說,這些值的選擇性低於平均選擇性。如果您擁有比這個隨機生成的樣本更現實的數據集,您可能會看到這種效果更明顯。例如,在真實世界的城市名稱上構建四個字符的前綴索引將對以 “聖” 和 “新” 開頭的城市的選擇性造成糟糕的影響,這些城市有很多。
現在我們已經為我們的示例數據找到了良好的值,以下是如何在列上創建前綴索引:
ALTER TABLE sakila.city_demo ADD KEY (city(7))
前綴索引提供了一種有用的方法來減少索引大小並提高性能。然而,它們也有某些缺點:MySQL 不能在 ORDER BY 或 GROUP BY 查詢中使用前綴索引,也不能將它們用作覆蓋索引。前綴索引可以有利的常見情況是使用長十六進制標識符。在前一章中,我們討論了更有效的存儲此類標識符的方法,但如果您使用的是不可修改的預打包解決方案呢?這在使用 MySQL 存儲基於長十六進制字符串的網站會話的 vBulletin 和其他應用程序中經常發生。在前八個字符(或大約)上添加索引通常會顯著提高性能,這對應用程序來說是完全透明的。
多列索引#
多列索引通常被誤解。常見的錯誤是將許多或所有列單獨索引,或以錯誤的順序索引列。我們將在下一節中討論列的順序。第一個錯誤,單獨索引許多列,在 SHOW CREATE TABLE
中有一個明顯的特徵:
CREATE TABLE t ( c1 INT, c2 INT, c3 INT, KEY(c1), KEY(c2), KEY(c3) )
[!info] 這種索引策略通常是當人們給出模糊但聽起來權威的建議,例如 “在 WHERE 子句中出現的列上創建索引。” 這個建議是非常錯誤的。它將導致最多只有一星的索引。這些索引的性能可能比真正的最佳索引慢幾個數量級。有時當您無法設計三星索引時,忽略 WHERE 子句並關注最佳行順序或創建覆蓋索引會更好。
對許多列的單獨索引不會幫助 MySQL 改善大多數查詢的性能。MySQL 5.0 及更新版本可以通過使用一種稱為 索引合併 的策略來稍微應對這種索引不良的表,該策略允許查詢有限地使用來自單個表的多個索引來定位所需的行。早期版本的 MySQL 只能使用單個索引,因此當沒有單個索引足夠好時,MySQL 通常會選擇表掃描。
例如,film_actor 表在 film_id 上有一個索引,在 actor_id 上也有一個索引,但在此查詢中,兩者都不是良好的選擇:
SELECT film_id, actor_id FROM sakila.film_actor
WHERE actor_id = 1 OR film_id = 1;
在舊版本的 MySQL 中,該查詢將產生表掃描,除非您將其寫成兩個查詢的聯合:
SELECT film_id, actor_id FROM sakila.film_actor WHERE actor_id = 1 UNION ALL
SELECT film_id, actor_id FROM sakila.film_actor WHERE film_id = 1 AND actor_id <> 1;
然而,在 MySQL 5.0 及更新版本中,查詢可以使用兩個索引,同時掃描它們並合併結果。
該算法有三種變體:對於 OR 條件的聯合,對於 AND 條件的交集,以及兩者組合的聯合。以下查詢使用兩個索引掃描的聯合,您可以通過檢查 Extra 列來看到:
EXPLAIN SELECT film_id, actor_id FROM sakila.film_actor WHERE actor_id = 1 OR film_id = 1
*************************** 1. row *************************** id: 1 select_type: SIMPLE table: film_actor type: index_merge possible_keys: PRIMARY,idx_fk_film_id key: PRIMARY,idx_fk_film_id key_len: 2,2 ref: NULL rows: 29 Extra: **Using union(PRIMARY,idx_fk_film_id); Using where**
需要記住的是,索引合併在某種程度上是一個昂貴的操作。因此,如果您在 EXPLAIN
中看到 “索引合併”,這可能是索引不良的表的指示:
-
當服務器交集索引(通常是 AND 條件)時,這通常意味著您需要一個包含所有相關列的單一索引,而不是必須合併的多個索引。
-
當服務器聯合索引(通常是 OR 條件)時,有時算法的緩衝、排序和合併操作會使用大量 CPU 和內存資源。這尤其適用於如果並非所有索引都非常具選擇性,因此掃描返回大量行以進行合併操作。當您在 EXPLAIN 中看到索引合併時,應檢查查詢和表結構,以查看這是否真的是您能得到的最佳結果。您可以使用 optimizer_switch 選項或變量禁用索引合併。您還可以使用 IGNORE INDEX。
選擇良好的列順序#
選擇列順序的舊規則是:將最具選擇性的列放在索引的前面。這個建議有多有用?在某些情況下,它可能會有所幫助,但通常比避免隨機 I/O 和排序重要性要小得多,考慮所有因素。(具體情況各異,因此沒有一刀切的規則。這本身應該告訴您,這條經驗法則可能沒有您想的那麼重要。)
將最具選擇性的列放在前面可能是一個好主意,當沒有排序或分組需要考慮時,因此索引的目的是僅優化 WHERE 查找。在這種情況下,設計索引以便能夠快速過濾掉行,對於僅在 WHERE 子句中指定索引前綴的查詢來說,確實可能效果很好。然而,這不僅取決於列的選擇性(整體基數),還取決於您用來查找行的實際值 —— 值的分佈。這與我們為選擇良好的前綴長度所探討的考量類似。您可能實際上需要選擇列的順序,以便對您將運行的大多數查詢具有最大的選擇性。
讓我們舉個例子查詢並更好地診斷它。
SELECT * FROM payment WHERE staff_id = 2 AND customer_id = 584;
我們應該在 (staff_id, customer_id) 上創建索引,還是應該反轉列的順序?
我們可以運行一些快速查詢來幫助檢查表中 值的分佈 以確定哪一列具有更高的選擇性。
讓我們計算 WHERE
子句中每個謂詞的基數。
SELECT SUM(staff_id = 2), SUM(customer_id = 584) FROM payment;
*************************** 1. row *************************** SUM(staff_id = 2): 7992 SUM(customer_id = 584): 30
根據經驗法則,我們應該將 customer_id 放在索引的第一位,因為該謂詞在表中匹配的行較少。然後我們可以再次運行查詢,以查看 staff_id 在此特定 customer_id 選定的行範圍內的選擇性。
SELECT SUM(staff_id = 2) FROM payment WHERE customer_id = 584;
SUM(staff_id = 2): 17
但如果您沒有具體的樣本可以運行,則最好使用舊的經驗法則,即查看整體的基數,而不僅僅是針對一個查詢:
SELECT COUNT(DISTINCT staff_id)/COUNT(*) AS staff_id_selectivity,
COUNT(DISTINCT customer_id)/COUNT(*) AS customer_id_selectivity,
COUNT(*)
FROM payment\G
staff_id_selectivity: 0.0001 customer_id_selectivity: 0.0373 COUNT(*): 16049
customer_id 具有更高的選擇性,因此再次的答案是將該列放在索引的第一位:
ALTER TABLE payment ADD KEY(customer_id, staff_id);
最終,雖然有關選擇性和基數的經驗法則是有趣的,但其他因素 —— 例如排序、分組以及查詢的 WHERE 子句中存在的範圍條件 —— 可能對查詢性能產生更大的影響。
聚集索引與非聚集索引#
聚集索引#
聚集索引不是一種單獨的索引類型。相反,它們是一種數據存儲方法。具體細節在不同的實現之間有所不同,但 InnoDB 的聚集索引實際上將 B - 樹索引和行一起存儲在同一結構中。
當表具有聚集索引時,其行實際上存儲在索引的葉頁中。
每個表只能有一個聚集索引,因為您不能同時將行存儲在兩個地方。
由於存儲引擎負責實現索引,因此並非所有存儲引擎都支持聚集索引,但我們在此專注於 InnoDB
。

請注意,記錄在聚集索引中的佈局。請注意,葉頁包含完整的行,但節點頁僅包含圖中所示的索引列。聚集索引(每個表僅有一個,通常是主鍵)。
某些數據庫服務器允許您選擇要聚集的索引,但截至本文撰寫時,MySQL 的內置存儲引擎都不支持此功能。InnoDB 通過主鍵聚集數據。
如果您未定義主鍵,InnoDB 將嘗試使用唯一的非空索引。如果沒有這樣的索引,InnoDB 將為您定義一個隱藏的主鍵,然後在該主鍵上進行聚集。
聚集可以幫助性能,但它也可能帶來負面和嚴重的問題。
聚集數據的優勢是什麼?
-
您可以將相關數據保持在一起。例如,在實現郵箱時,您可以按 user_id 聚集,以便通過僅從磁碟中提取幾個頁面來檢索單個用戶的所有消息。如果不使用聚集,每條消息可能需要自己的磁碟 I/O。
-
數據訪問速度快。聚集索引將索引和數據一起保存在一個 B - 樹中,因此從聚集索引檢索行通常比在非聚集索引中進行相應查找要快。
-
使用覆蓋索引的查詢可以使用葉節點中包含的主鍵值。
缺點是什麼?
-
聚集對 I/O 限制的工作負載提供最大的改進。如果數據適合內存,則訪問的順序並不重要,因此聚集並不會帶來太多好處。
-
插入速度在很大程度上取決於插入順序。按主鍵順序插入行是將數據加載到 InnoDB 表中的最快方法。如果您在未按主鍵順序加載大量數據後,最好使用 OPTIMIZE TABLE 重新組織表。
-
更新聚集索引列的成本很高,因為這會迫使 InnoDB 將每個更新的行移動到新位置。
-
基於聚集索引的表在插入新行或更新行的主鍵時,可能會發生頁面拆分,這樣行必須移動。當行的鍵值指示該行必須放置到已滿的頁面中時,頁面拆分會發生。存儲引擎必須將頁面拆分為兩個以容納該行。頁面拆分可能會導致表在磁碟上使用更多空間。
-
聚集表在全表掃描時可能會更慢,特別是當行的密度較低或因頁面拆分而非順序存儲時。
-
二級索引訪問需要兩次索引查找,而不是一次。最後一點可能有點令人困惑。為什麼二級索引需要兩次索引查找?答案在於二級索引存儲的 “行指針” 的性質。請記住,葉節點不存儲對引用行的物理位置的指針;相反,它存儲行的主鍵值。
這意味著要從二級索引中找到一行,存儲引擎首先在二級索引中找到葉節點,然後使用那裡存儲的主鍵值導航到主鍵並找到該行。這是雙重工作:兩次 B - 樹導航,而不是一次。
InnoDB 和 MyISAM 數據佈局的比較#
聚集和非聚集數據佈局之間的差異,以及主索引和二級索引之間的相應差異,可能會令人困惑和驚訝。讓我們看看 InnoDB 和 MyISAM 如何佈局以下表:
CREATE TABLE layout_test ( col1 int NOT NULL, col2 int NOT NULL, PRIMARY KEY(col1), KEY(col2));
假設我們已經用隨機順序插入的 pk 從 1 到 10,000 填充了表,然後使用 OPTIMIZE TABLE
進行優化。換句話說,數據在磁碟上以最佳方式排列,但行可能是隨機順序。col2 的值在 1 到 100 之間隨機分配,因此有很多重複值。
須知,非聚集索引設計並不總是能夠提供單操作行查找。當行發生變化時,它可能不再適合其原始位置,因此您可能會最終得到碎片化的行或表中的 “轉發地址”,這兩者都會導致查找行的工作量增加。


MyISAM 的數據佈局#
MyISAM 將行按插入順序存儲在磁碟上。

由於行是固定大小的,MyISAM 可以通過從表的開頭尋找所需的字節數來找到任何行。(如我們所示,MyISAM 並不總是使用 “行號”;它根據行是固定大小還是可變大小使用不同的策略。)
每個索引中的葉節點可以簡單地包含行號。
InnoDB 的數據佈局#
按主鍵順序插入行的 InnoDB#

/** 聚集索引 **/
ALTER TABLE dbo.MyTable
ADD CONSTRAINT PK_MyTable PRIMARY KEY(Id);
/**非聚集索引**/
ALTER TABLE dbo.MyTable
ADD CONSTRAINT UC_MyTable_Id UNIQUE(Id);


非聚集索引假設我們已經在名稱列上創建了一個非聚集索引。


行定位器包含:
-
聚集鍵(employee_id)
-
實際行 [name]
聚集索引和非聚集索引共同工作以查找數據!
查找實際行的步驟是什麼?

-
SQL 服務器使用非聚集索引在名稱列上快速找到索引中的員工條目。
-
聚集索引(employee_id)用於查找實際行!
在 SQL Server 中,我們從上到下、從右到左閱讀執行計劃!

當查詢通過聚集索引樹導航到基表數據時,這稱為 聚集索引查找。聚集索引包含基表數據本身,這就是為什麼您可以創建一個聚集索引。

非聚集索引 與基數據分開,基數據可以作為聚集索引存在,直接可用的數據可能會受到限制,因為通常非聚集索引僅包括表中的一部分列。
如果請求的列中不在索引中的值,查詢可能會使用前面提到的引用導航回基數據。如果查詢優化器認為這樣做的成本太高,它可能會選擇掃描基數據,而不是使用索引。
非聚集索引與基數據分開帶來了一些重要特徵。
過濾索引 只包含滿足用戶定義謂詞的行,並且要創建這些索引,您在索引定義中使用 WHERE
子句,因此不能使用聚集索引,因為它必須包含表中的所有數據。
CREATE NONCLUSTERED INDEX IX_PhoneBook_NCI
ON dbo.PhoneBook(LastName, FirstName)
WHERE (LastName >= 'Yousef');
總結:聚集索引是一種將數據整體表示的方法,非聚集索引是一種物理上分開的結構,引用基數據並且可以有不同的排序順序。