高效利用mysql索引指南
前言
mysql 相信大部分人都用過,索引肯定也是用過的,但是你知道如何建立恰當的索引嗎?在資料量小的時候,不合適的索引對效能並不會有太大的影響,但是當資料逐漸增大時,效能便會急劇的下降。
本篇是對 mysql 索引的一個歸納總結,如果有錯誤的地方,記得評論指出哦。
索引基礎
我們都有都知道查字典的步驟,是先在索引頁中找到這個字的頁碼,然後再到對應的頁碼中檢視這個字的資訊。mysql 的索引方法也是和這個類似的,先在索引中找到對應值,然後根據匹配的索引記錄找到對應的資料行。假如有下面的 sql 語句:
select * from student where code='2333'
加入 code 列上建立有索引,mysql 將使用該索引找到值為'2333'的資料行,然後讀取資料行的所有資料返回。
索引型別
B-Tree 索引
(不是 B 減樹,就是 B 樹),絕大多數的索引型別都是 B-Tree 的(或者是 B-Tree 的變體),通常我們使用的也是這類索引。Mysql 中 MyISAM 儲存引擎使用的是 B-tree,InnoDB 使用的是 B+Tree,B 樹和 B+樹的區別自行百度。
樹結構的索引能夠加快訪問資料的速度,儲存引擎不再需要全表掃描來獲取所需的資料,取而代之的是從樹的根節點來進行二分搜尋,總所周知二分搜尋的速度是相當快的,因此我們能夠利用索引來極大的提高查詢速度。B-Tree 支援以下幾種型別的查詢:
假設再 student 表中僅有:name,age,weight 這樣一個多列索引,下面的查詢都能利用到此索引
- 全值匹配
和索引列中的所有列進行匹配。比如查詢name='abc' and age=12,這裡用到了第一列和第二列
- 匹配最左前列
只是用索引的開頭部分,比如查詢name='ggg'只使用索引的第一列,查詢name='ggg' and age=12是用索引的第一、二列。
- 匹配列字首
也可以只匹配某一列的開頭部分,比如查詢name lik 'g%',查詢 name 以 g 開頭的記錄。這裡用到了第一列
- 匹配範圍值
可用於匹配範圍值,比如查詢name > 'abc' and name < 'bcd'
- 精確匹配某一列並範圍匹配另外一列
用於匹配多列,比如查詢name='abc' and age > 12。
總的來看,可以發現 B-Tree 索引適用於根據最左字首的查詢,也就是查詢欄位欄位順序要和索引欄位順序一樣,且以第一個索引欄位開頭。比如查詢name,name and age,name and age and weight都能使用索引,但是查詢age,age and name不能使用索引。
雜湊索引
hash 索引基於 hash 表實現,只有精確匹配索引所有列才會生效。MySQL 中只有 Memory 引擎顯示支援雜湊索引,同時也是其預設索引。
InnoDB 無法建立 hash 索引,但是它有一個功能叫自適應hash索引,當某些索引值使用非常頻繁時,引擎會在記憶體中基於 B-Tree 索引之上再建立一個 hash 索引,這樣就讓 B-Tree 索引也有了一點 hash 索引的優點。這個功能是一個完全自動的、內部的行為,也就是無法手動控制或配置。
高效能索引策略
下面是一些常見的索引策略。
獨立的列
這個很簡單,如果查詢中的列不是獨立,便無法使用索引,比如:
select * from student where age+1=12
即使 age 列有索引,上面的查詢語句也是無法利用索引的。
字首索引和索引選擇性
如果需要索引很長的字串列,直接建立索引,會讓索引佔用更多的空間且速度較慢。一個優化策略是模擬 hash 索引:給列計算一個 hash 值,並在 hash 值列建立索引。
另外一個辦法就是建立字首索引。只索引這個欄位開始的部分字元,這樣可以極大的解決空間佔用,索引建立速度也會快很多。但是這樣也有如下弊端:
- 降低了索引選擇性,如果多個字串字首相同便無法區分,還需要進行字串對比。
- 不支援order by,group by,原因顯而易見,只索引了部分字元,無法完全區分。
這裡的關鍵是確定索引多少個字符合適。既要避免長度過大,還要有足夠的索引選擇性。有以下兩種辦法來幫助確定索引字元數:
索引欄位字首資料分佈均勻。也就是以索引字元開頭的字串數目分佈均勻,比如索引 name 欄位的前 3 個字元,下面的結果是比較合理(只取排名前 8 的):
數目 | 索引前三個字元 |
---|---|
500 | abc |
465 | asd |
455 | acd |
431 | zaf |
430 | aaa |
420 | vvv |
411 | asv |
512 |
如果每一列的資料都比較大,說明區分度還不高需要增大索引字元數,直到這個字首的選擇性接近完整列的索引性,也就是前面的資料要儘可能的小。
計算完整列的選擇性,並使字首的選擇性接近於完整列的選擇性。下面語句使用者計算完整列選擇性:
-- 不同字串的數目/總的數目就是完整列選擇性 select count(distinct name)/count(*) from person;
下面語句計算索引前 3 個欄位選擇性:
-- 前3個字元不同的字串資料/總的資料 select count(distincy left(city,3))/count(*) from person
不斷增大索引字元數目,直到選擇性接近完整列選擇性且繼續增大資料選擇性提升幅度不大的時候。
建立方法
-- 假設最佳長度為4 alter table person add key (name(4));
多列索引
不少人有這樣的誤解,如果一個查詢用有多個欄位 ‘and'查詢,那麼給每個欄位都建立索引不就能最大化提高效率了?事實並不是如此,mysql 只會選擇其中一個欄位來進行索引查詢。這種情況下應該建立多列索引(又叫聯合索引),就能利用多個索引欄位了,注意索引列順序要和查詢的順序一致。
在 5.0 及以上版本中引入了“索引合併”的策略。一定程度上也可以使用多個單列索引,比如下面的查詢:
-- mysql會分別使用name和age索引查出資料然後合併 -- 如果使and則查出資料後再對比取交集 select * from person where name = "bob" or age=12
但是不推薦這麼做,and 或 or條件過多會耗費大量的 CPU 和記憶體在演算法的快取、排序和合並操作上。
選擇合適的索引列順序
在一個多列 B-Tree 索引中,索引列的順序意味著索引首先是按照最左列進行排序,然後是第二列…索引一個良好的多列索引應該是將選擇性最高的索引放在最前面,然後依次降低,這樣才能更好的利於索引。選擇性計算方發見:字首索引 小節。
聚族索引
聚族索引不是一種單獨的索引型別,而是一種資料儲存方法,具體的細節依賴其實現方式。
InnoDB 的聚族索引實際是在同一個結構中儲存索引值和資料行。因為不能同時將資料行放在兩個不同的地方,所以一個表只能有一個聚族索引。InnoDB 的聚族索引列為“主鍵列”。
如果沒有定義主鍵,InnoDB 會選擇一個唯一的非空索引代替。如果這樣的索引也沒有,InnoDB 會隱式定義一個主鍵來作為聚族索引。
聚族索引的主要優點是:可以把相關資料儲存在一起,減少磁碟 IO,提高查詢效率。但是也有缺點:
- 插入順序嚴重依賴於插入順序。按照主鍵的順序插入是速度最快的方式,否則可能會導致頁分裂的問題出現,會佔用更多的磁碟空間,掃描速度也會變慢。可通過OPTIMIZE TABLE重新組織表。
- 更新聚族索引列代價很高,因為索引值變了,行資料也會跟著索引移動到新的位置上。
- 二級索引(非聚族索引)訪問行資料需要兩次索引查詢,因為二級索引葉子節點儲存的並不是行資料的物理位置,而是行的主鍵值,再通過主鍵值到聚族索引中取行資料。
覆蓋索引
簡單來說就是一個索引覆蓋了需要查詢的列欄位,這樣就不需要再到聚族索引中利用主鍵進行二次查詢,在一個二級索引中就能取到所需的資料。
InnoDB 的索引會在葉子節點中儲存索引值,因此如果要查詢的欄位全部包含在某個索引中,且這個索引被使用了,那麼就能極大的提高查詢速度。比如如下查詢語句:
-- name有索引的情況下,直接從索引的葉子節點中取name值返回,無需二次查詢 select name from person where name = 'abc' -- 如果存在`name,age`聚合索引,也會直接返回資料,無需二次查詢 select name,age from person where name='abc' and age=12
使用索引進行排序
mysql 的排序操作也是可以利用索引的,只有當索引的列順序和ORDER BY的順序完全一致,並且所有列的排序方法(正序或者倒序)也一樣時,才能夠使用索引來進行排序。注意:排序的欄位可以比對應的索引欄位少,但是順序必須一致。如下:
-- 假設有:(name,sex)聯合索引 -- 可使用索引排序 select ... order by name desc,age desc select ... order by name desc,age desc,sex desc -- 不可使用排序 select ... order by name desc,sex desc select ... order by name desc,age asc
結束
本篇基於 mysql 5.5 的版本,更新的版本可能會有不一樣的策略。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。