什么是聚簇索引和非聚簇索引,如何理解回表、索引下推
聚簇索引(Clustered Index)和非聚簇索引(Non-clustered Index)是數據庫中的兩種索引類型,它們在組織和存儲數據時有不同的方式。
聚簇索引
聚簇索引簡單理解就是將數據與索引放在一起,找到索引即找到了數據。換句話說,對于聚簇索引,其非葉子節點上存儲的是索引字段的值,而葉子節點上存儲的是對應記錄的整行數據。
在 InnoDB 中,聚簇索引(Clustered Index)是指按照每張表的主鍵構建的一種索引方式。它將表數據按照主鍵的順序存儲在磁盤上,確保了行的物理存儲順序與主鍵的邏輯順序相同。這種索引方式使得查找聚簇索引的速度非常快。
非聚簇索引是指將索引與數據分開存儲的一種方式。在非聚簇索引中,葉子節點包含索引字段的值以及指向數據頁數據行的邏輯指針。
在 InnoDB 中,非聚簇索引(Non-clustered Index)是根據非主鍵字段創建的索引,通常稱為二級索引。它不影響表中數據的物理存儲順序,而是單獨創建一張索引表,用于存儲索引列和對應行的指針。
在 InnoDB 中,主鍵索引就是聚簇索引,而非主鍵索引則是非聚簇索引。因此,在 InnoDB 中:
對于聚簇索引,其非葉子節點上存儲的是索引值,而葉子節點上存儲的是整行記錄。
對于非聚簇索引,其非葉子節點上存儲的是索引值,而葉子節點上存儲的是主鍵的值以及索引值。
因此,通過非聚簇索引進行查詢時,需要進行一次回表操作,即先通過索引查找到主鍵 ID,然后再通過 ID 查詢所需字段。
沒有創建主鍵怎么辦?
在 InnoDB 中,如果表結構中沒有定義主鍵,數據庫會自動為每行記錄添加一個隱藏的主鍵,通常稱為 db_row_id 字段。這個隱藏主鍵會確保每行記錄都有一個唯一的標識符。
如果表中沒有合適的唯一索引可用作聚簇索引,數據庫會使用這個隱藏主鍵來構建聚簇索引。這樣可以確保每行記錄都有一個物理上的唯一標識符,并且能夠保持索引的唯一性和快速查詢的特性。
什么是回表,怎么減少回表的次數?
在 InnoDB 中,索引 B+樹的葉子節點存儲了整行數據的是主鍵索引,也被稱為聚簇索引。而索引 B+樹的葉子節點存儲了主鍵的值的是非主鍵索引,也被稱為非聚簇索引。
在數據存儲方面,主鍵(聚簇)索引的 B+樹的葉子節點直接包含了我們要查詢的整行數據。而非主鍵(非聚簇)索引的葉子節點則包含了主鍵的值。
因此,當我們通過非聚簇索引進行查詢時,首先會通過非聚簇索引查找到主鍵的值,然后需要再通過主鍵的值進行一次查詢才能獲取到我們要查詢的數據。這個過程稱為回表。
因此,在 InnoDB 中,使用主鍵進行查詢效率更高,因為這個過程不需要回表。此外,通過依賴覆蓋索引、索引下推等技術,我們可以通過優化索引結構和 SQL 語句來減少回表的次數。
什么是索引覆蓋、索引下推?
覆蓋索引
覆蓋索引是指查詢語句的執行只需從索引中獲取所需數據,而無需從數據表中讀取。也可以稱之為實現了索引覆蓋。
當一條查詢語句符合覆蓋索引條件時,MySQL 只需通過索引就能返回查詢所需數據,而不需要進行索引查找后再返回表操作,從而減少 I/O,提高效率。
例如,在表 covering_index_sample 中有一個普通索引 idx_key1_key2(key1,key2)。
當我們執行以下 SQL 語句時:
javascript
SELECT key2 FROM covering_index_sample WHERE key1 = 'keytest';
此時可以通過覆蓋索引查詢,無需進行回表操作。
但是對于以下 SQL 語句,雖然是索引覆蓋,但由于不符合最左前綴匹配,無法利用索引(會掃描索引樹):
javascript
SELECT key1 FROM covering_index_sample WHERE key2 = 'keytest';
另外,如果查詢語句中需要的信息不包含在聯合索引中,那么就無法使用索引覆蓋。例如:
javascript
SELECT key2, key3 FROM covering_index_sample WHERE key1 = 'keytest';
索引下推
索引下推是 MySQL 5.6 引入的一種優化技術,默認開啟,可通過設置 SET optimizer_switch = 'index_condition_pushdown=off'; 來關閉。
它的工作原理如下:假設 people 表中(zipcode,lastname,firstname)構成一個索引。考慮以下查詢:
javascript
SELECT * FROM people WHERE zipcode='95054' AND lastname LIKE '%etrunia%' AND address LIKE '%Main Street%';
如果沒有使用索引下推技術,MySQL 會通過 zipcode='95054'從存儲引擎中查詢對應的數據,然后將結果返回到 MySQL 服務端,接著 MySQL 服務端再基于lastname LIKE '%etrunia%' 和 address LIKE '%Main Street%'來判斷數據是否符合條件。
而如果使用了索引下推技術,MySQL 首先會返回符合 zipcode='95054'的索引,然后根據lastname LIKE '%etrunia%'來判斷索引是否符合條件。如果符合條件,則根據該索引定位對應的數據;如果不符合,則直接拒絕。有了索引下推優化,可以在有 like 條件查詢的情況下,減少回表次數。
當一條 SQL 使用到索引下推時,執行計劃中的 extra 字段的內容會顯示為 "Using index condition"。
索引下推不止 like
上面的例子中,提到了 like,包括 MySQL 官網中也只提到了 like,但是其實不止有 Like。因為我認為索引下推其實是解決索引失效帶來的效率低的問題的一種手段。
所以當聯合索引中,某個非前導列因為索引失效而要進行掃表并回表時,就可以進行索引下推優化了。