MySQL的索引有不少種類型,能夠爲不一樣的場景提供更好的性能。而B-Tree索引是最爲常見的MySQL索引類型,通常談論MySQL索引時,若是沒有特別說明,就是指B-Tree索引。本文就詳細講解一下B-Tree索引的的底層結構,使用原則和特性。 爲了節約你的時間,本文的主要內容以下:html
B-Tree索引使用B-Tree來存儲數據,固然不一樣存儲引擎的實現方式不一樣。B-Tree一般意味着全部的值都是按順序存儲的,而且每個葉子頁到根的距離相同,圖1展現了B-Tree索引的抽象表示,由此能夠看出MySQL的B-Tree索引的大體工做機制。mysql
B-Tree索引的底層數據結構通常是B+樹,其具體數據結構和優點這裏就不做詳細描述,下圖展現了B-樹索引的抽象表示,大體反應了MyISAM索引是如何工做的,而InnoDB使用的結構有所不一樣。算法
MySQL能夠在單獨一列上添加B-Tree索引,也能夠在多列數據上添加B-Tree索引,多列的數據按照添加索引聲明的順序組合起來,存儲在B-Tree的頁中。假設有以下數據表:sql
CREATE TABLE People (
last_name varchar(50) not null,
first_name varchar(50) not null,
birthday date not null,
gender enum('m','f') not null
key(last_name, first_name, birthday)
);
複製代碼
對於表中的每一行數據,索引中包含了last_name,first_name和birthday列的值,下圖展現了該索引是如何組織數據的存儲的。數據庫
B-Tree索引使用B-Tree做爲其存儲數據的數據結構,其使用的查詢規則也由此決定。通常來講,B-Tree索引適用於全鍵值、鍵值範圍和鍵前綴查找,其中鍵前綴查找只適用於根據最左前綴查找。B-Tree索引支持的查詢原則以下所示:性能優化
全值匹配:全值匹配指的是和索引中的全部列進行匹配,bash
匹配最左前綴:前邊提到的索引能夠用於查找全部姓Allen的人,即只使用索引中的第一列。微信
匹配列前綴:也能夠只匹配某一列的值的開頭部分。例如前面提到的索引可用於查找全部以J開頭的姓的人。這裏也只用到了索引的第一列。數據結構
匹配範圍值:例如前邊提到的索引可用於查找姓在Allen和Barrymore之間的人。這裏也只使用了索引的第一列。工具
精確匹配某一列並範圍匹配另一列:前邊提到的索引也可用於查找全部姓爲Allen,而且名字是字母K開頭(好比Kim,Karl等)的人。即第一列last_name全匹配,第二列first_name範圍匹配。
由於索引樹的節點是有序的,因此除了按值查找以外,索引還能夠用於查詢中的ORDER BY操做(按順序查找),若是ORDER BY子句知足前面列出的幾種查詢類型,則這個索引也能夠知足對應的排序需求。
下面是一些關於B-Tree索引的限制:
聚簇索引並非一種單獨的索引類型,而是一種數據存儲方式。具體的細節依賴於其實現方式,可是InnoDB的聚簇索引實際上在同一個結構中保存了B-Tree索引和數據行。
當表有聚簇索引時,它的數據行實際上存放在索引的葉子頁中,這也就是說數據行和相鄰的鍵值緊湊地存儲在一塊兒。
下圖展現了聚簇索引中的記錄是如何存放的。注意到,葉子頁包含了行的所有數據行,可是節點頁只包含了索引列。
聚簇索引可能對性能有幫助,但也可能致使嚴重的性能問題。聚簇的數據是有一些重要的優勢:
若是在設計表和查詢時能充分利用上面的優勢,那麼就能極大地提高性能。同時,聚簇索引也有一些缺點:
聚簇索引和非聚簇索引的數據分佈有區別,以及對應的主鍵索引和二級索引的數據分佈也有區別,一般會讓人感到困惑和意外。下圖展現了MyISAM和InnoDB的不一樣索引和數據存儲方式。
MyISAM的數據分佈很是簡單,按照數據插入的順序存儲在磁盤上,主鍵索引和二級索引的葉節點存儲着指針,指向對應的數據行。
InnoDB中,聚簇索引「就是」表,因此不會像MyISAM那樣須要獨立的行存儲。聚簇索引的每一個葉節點都包含了主鍵值和全部的剩餘列(在此例中是col2)。
InnoDB的二級索引和聚簇索引很不一樣。InnoDB二級索引的葉節點中存儲的不是「行指針」,而是主鍵值,並以此做爲指向行的「指針」。
MySQL並不支持鬆散索引掃描,也就是沒法按照不連續的方式掃描一個索引。一般,MySQL的索引掃描須要先定義一個起點和終點,即便須要的數據只是這段索引中不多數的幾個,MySQL仍然須要掃描這段索引中的每一個條目。
下面,咱們經過一個示例說明這點,假設咱們有以下索引(a,b),有下面的查詢:
mysql>SELECT * FROM tb1 WHERE b BETWEEN 2 AND 3;
複製代碼
由於索引的前導字段是列a,可是在查詢中只指定了字段b,MySQL沒法使用這個索引,從而只能經過全表掃描找到匹配的行,以下圖所示。
瞭解索引的物理結構的話,不難發現還能夠有一個更快的辦法執行上面的查詢。索引的物理結構(不是存儲引擎的API)是的能夠先掃描a列第一個值對應的b列的範圍,而後再跳到a列第二個不不一樣值掃描對應的b列的範圍。下圖展現了若是由MySQL來實現這個過程會怎樣。
注意到,這時就無須再使用WHERE子句過濾,由於鬆散索引掃描已經跳過了全部不須要的記錄。
MySQL 5.0以後的版本,在某些特殊的場景下是可使用鬆散索引掃描的,例如,在一個分組查詢中須要找到分組的最大值和最小值:
mysql> EXPLAIN SELECT actor_id, MAX(film_id)
-> FROM sakila.film.film_actor
-> GROUP BY actor_id;
********************************************* 1. row ***********************************
id: 1
select_type: SIMPLE
table: film_actor
type: range
possible_keys: NULL
key: PRIMARY
key_len: 2
ref: NULL
rows: 396
Extra: Using index for group-by
複製代碼
在EXPLAIN中的Extra字段顯示"Using index for group-by",表示這裏將使用鬆散索引掃描。
索引除了是一種查找數據的高效方式以外,也是一種列數據的直接獲取方式。MySQL可使用索引來直接獲取列的數據,這樣就不須要讀取數據行。若是一個索引包含全部須要查詢的字段的值,咱們就稱之爲「覆蓋索引」。 覆蓋索引是很是有用的工具,可以極大地提升性能。SQL查詢只須要掃描索引而無需回表,會帶來不少好處:
當發起一個被覆蓋索引的查詢(也叫索引覆蓋查詢)時,在EXPLAIN的Extra列能夠看到"Using Index"的信息。例如,表sakila.inventory有一個多列索引(store_id, film_id)。MySQL若是隻須要訪問這兩列,就可使用這個索引作覆蓋索引,以下所示:
mysql> EXPLAIN SELECT store_id, film_id FROM sakila.inventory
*********************************1.row***************************************
id:1
select_type:SIMPLE
table:inventory
type:index
possible_keys:NULL
key:idx_store_id_film_id
key_len:3
ref:NULL
rows:4673
Extra:Using Index
複製代碼
訂閱最新文章,歡迎關注個人微信公衆號