索引是存儲引擎用于快速找到記錄的一種數據結構, 它能提高查詢性能.
索引基礎
索引是在存儲引擎層實現的, MySQL支持以下幾種索引類型:
索引的類型
B-Tree索引
InnoDB使用了B+Tree, 特點是:
- 非葉子節點只存儲鍵值信息。
- 所有葉子節點之間都有一個鏈指針。
- 數據記錄都存放在葉子節點中。
B+Tree的高度一般都在2~4層。mysql的InnoDB存儲引擎在設計時是將根節點常駐內存的,也就是說查找某一鍵值的行記錄時最多只需要1~3次磁盤I/O操作.
數據庫中的B+Tree索引可以分為聚集索引(clustered index)和輔助索引(secondary index)。上面的B+Tree示例圖在數據庫中的實現即為聚集索引,聚集索引的B+Tree中的葉子節點存放的是整張表的行記錄數據。輔助索引與聚集索引的區別在于輔助索引的葉子節點并不包含行記錄的全部數據,而是存儲相應行數據的聚集索引鍵,即主鍵。當通過輔助索引來查詢數據時,InnoDB存儲引擎會遍歷輔助索引找到主鍵,然后再通過主鍵在聚集索引中找到完整的行記錄數據.
InnoDB存儲引擎中有頁(Page)的概念,頁是其磁盤管理的最小單位, InnoDB在把磁盤數據讀入到磁盤時會以頁為基本單位, 通過如下命令查看頁的大小:
mysql> show variables like 'innodb_page_size';
上表的索引對以下類型的查詢有效:
- 全值匹配全值匹配: 指的是和索引中的所有列進行匹配,例如前面提到的索引可用于查找姓名為Cuba Allen、出生于1960-01-01的人。
- 匹配最左前綴前面: 提到的索引可用于查找所有姓為Allen的人,即只使用索引的第一列。
- 匹配列前綴: 也可以只匹配某一列的值的開頭部分。例如前面提到的索引可用于查找所有以J開頭的姓的人。
- 匹配范圍值: 例如前面提到的索引可用于查找姓在Allen和Barrymore之間的人。這里也只使用了索引的第一列。
- 精確匹配某一列并范圍匹配另外一列: 前面提到的索引也可用于查找所有姓為Allen,并且名字是字母K開頭(比如Kim、Karl等)的人。
- 只訪問索引的查詢: B-Tree通常可以支持“只訪問索引的查詢”,即查詢只需要訪問索引,而無須訪問數據行。后面我們將單獨討論這種“覆蓋索引”的優化。
B+Tree索引適用于全鍵值, 鍵值范圍或鍵前綴查找. 其中鍵前綴查找只適用于根據最左前綴的查找. 且因為索引樹中的節點是有序的, 索引除了按值查找外, 索引還可以用于查詢中的ORDER BY操作. 即滿足上面索引查詢的時候, 也可以滿足對應的排序要求(不需再花費額外的內存和時間).
哈希索引
哈希索引基于哈希表實現,只有精確匹配索引的所有列,索引才會生效。
哈希索引的限制:
- 哈希索引只存儲哈希值和行指針,不存儲字段數據值,因此無法從索引中查詢數據
- 哈希索引無法順序存儲,無法進行排序
- 哈希索引不支持部分匹配,必須精確匹配所有索引列
- 哈希索引不支持范圍查找
- 哈希沖突會影響性能
可以創建自定義哈希索引(見書上的例子), 此時要避免沖突問題, 必須在where條件中帶入hash值和對應列值, 只需要根據Hash值做快速的整數比較找到索引條目, 然后一一比較返回對應的行.
空間數據索引(R-Tree)
用作地理數據存儲, MySQL支持的不完善, 比較好的是PostgresSQL的PostGIS.
全文索引
它查找的是文本中的關鍵詞, 而非直接比較索引中的值. 可以在相同的列上同時創建全文索引和基于值的B-Tree索引不會有沖突, 全文索引適用于MATCH AGAINST操作, 而非普通的WHERE條件操作. 第七章會討論更多全文索引的細節.
索引的優點
- 大大減少服務器需要掃描的數據量
- 可以幫助服務器避免排序和臨時表
- 可以將隨機I/O變為順序I//O
一星系統:
索引將相關的記錄放到一起得一星.
二星系統:
索引中的數據順序和查找中的排列順序一致得二星.
三星系統:
索引中的列包含了查詢中需要的所有列得三星.
當然,索引也并不總是最好的工具。只有當索引幫助存儲引擎快速查找到記錄帶來的好處大于其帶來的額外工作時,索引才是有效的。非常小的表,大部分直接全表掃描更有效;中到大型的表,索引非常有效。特大型的表,則建立和使用索引的代價將隨之增長,需要一種技術可以區分出查詢需要的一組數據,如分區技術
高性能的索引策略
獨立的列
列不要進行參與計算:
where id+1=5
應該改成where id=4
前綴索引和索引選擇性
一種選擇索引的技巧是: 選擇足夠長的前綴保證較高的選擇性, 同時又不能太長(以便節約空間).
索引的選擇性是指: 不重復的索引值(也稱為基數, cardinality)和數據表的總記錄數(#T)的比值, 范圍從1/#T到1之間. 索引的選擇性越高則查詢效率越高.
balabala, 若找到了合適的前綴長度, 則可用以下語句創建前綴索引:
mysql> ALTER TABLE sakila.city_demo ADD KEY (city(7));
查詢的時候where city='xxxxxxxxxx'
自動會用到前綴索引
前綴索引優點是: 使索引更小.
缺點是: MySQL無法使用前綴索引做ORDER BY和GROUP BY, 也無法使用前綴索引做覆蓋掃描.
多列索引
這里指多個單列索引, 不建議.
選擇合適的索引列順序
在一個多列B-Tree索引中, 索引列的順序意味著索引先按照最左邊進行排序, 其次是第二列, 等等.
經驗法則: 將選擇性最高的列放在索引最前列. 但特殊情況有其他的標準.
聚簇索引
InnoDB的聚簇索引實際在同一個結構中保存了B-Tree索引和數據行.
數據行存儲在葉子頁中, "聚簇"表示數據行和相鄰的鍵值緊湊地存儲在一起. 一個表只能有一個聚簇索引.
假設建表語句為:
mysql> create table T(
id int primary key,
k int not null,
name varchar(16),
index (k))engine=InnoDB;
主鍵索引的葉子節點存的是整行數據。在 InnoDB 里,主鍵索引也被稱為聚簇索引(clustered index)。
非主鍵索引的葉子節點內容是主鍵的值。在 InnoDB 里,非主鍵索引也被稱為二級索引(secondary index)。
如果語句是 select * from T where ID=500,即主鍵查詢方式,則只需要搜索 ID 這棵 B+ 樹;如果語句是 select * from T where k=5,即普通索引查詢方式,則需要先搜索 k 索引樹,得到 ID 的值為 500,再到 ID 索引樹搜索一次。這個過程稱為回表。
索引維護
B+ 樹為了維護索引有序性,在插入新值的時候需要做必要的維護。
以上面這個圖為例,如果插入新的行 ID 值為 700,則只需要在 R5 的記錄后面插入一個新記錄。如果新插入的 ID 值為 400,就相對麻煩了,需要邏輯上挪動后面的數據,空出位置。而更糟的情況是,如果 R5 所在的數據頁已經滿了,根據 B+ 樹的算法,這時候需要申請一個新的數據頁,然后挪動部分數據過去。這個過程稱為頁分裂。
在這種情況下,性能自然會受影響。除了性能外,頁分裂操作還影響數據頁的利用率。原本放在一個頁的數據,現在分到兩個頁中,整體空間利用率降低大約 50%。當然有分裂就有合并。當相鄰兩個頁由于刪除了數據,利用率很低之后,會將數據頁做合并。合并的過程,可以認為是分裂過程的逆過程。
MyISAM的數據存儲
MyISAM的行數據按照數據插入的順序存儲在磁盤上, 索引也是B-Tree, 不過B-Tree的葉子節點存儲的是行號(行指針). MyISAM的主鍵和其他索引在結構上沒有不同. 如下圖所示:
要注意的是: InnoDB的二級索引的葉子節點存儲的不是行指針, 而是主鍵值, 這樣的好處是當行移動或數據頁分裂時, 無須更新二級索引中的主鍵值.
在InnoDB表中按主鍵順序插入行
盡量使用AUTO_INCREMENT自增列, 保證數據行是按順序寫入. 如果非順序插入, 會有以下幾個缺點:
- 寫入的目標頁可能已經刷到磁盤上并從緩存中移除, 不得不先找到并從磁盤讀取目標頁到內存中.
- 因為寫入是亂序, InnoDB不得不頻繁的做頁分裂操作, 這時候需要申請一個新的數據頁,然后挪動部分數據過去, 性能受影響.
- 頻繁頁分裂, 頁會變得稀疏和碎片.