高效利用mysql索引指南
前言
mysql 相信大部分人都用過,索引肯定也是用過的,但是你知道如何創(chuàng)建恰當(dāng)?shù)乃饕龁??在?shù)據(jù)量小的時候,不合適的索引對性能并不會有太大的影響,但是當(dāng)數(shù)據(jù)逐漸增大時,性能便會急劇的下降。
本篇是對 mysql 索引的一個歸納總結(jié),如果有錯誤的地方,記得評論指出哦。
索引基礎(chǔ)
我們都有都知道查字典的步驟,是先在索引頁中找到這個字的頁碼,然后再到對應(yīng)的頁碼中查看這個字的信息。mysql 的索引方法也是和這個類似的,先在索引中找到對應(yīng)值,然后根據(jù)匹配的索引記錄找到對應(yīng)的數(shù)據(jù)行。假如有下面的 sql 語句:
select * from student where code='2333'
加入 code 列上建立有索引,mysql 將使用該索引找到值為'2333'的數(shù)據(jù)行,然后讀取數(shù)據(jù)行的所有數(shù)據(jù)返回。
索引類型
B-Tree 索引
(不是 B 減樹,就是 B 樹),絕大多數(shù)的索引類型都是 B-Tree 的(或者是 B-Tree 的變體),通常我們使用的也是這類索引。Mysql 中 MyISAM 存儲引擎使用的是 B-tree,InnoDB 使用的是 B+Tree,B 樹和 B+樹的區(qū)別自行百度。
樹結(jié)構(gòu)的索引能夠加快訪問數(shù)據(jù)的速度,存儲引擎不再需要全表掃描來獲取所需的數(shù)據(jù),取而代之的是從樹的根節(jié)點(diǎn)來進(jìn)行二分搜索,總所周知二分搜索的速度是相當(dāng)快的,因此我們能夠利用索引來極大的提高查詢速度。B-Tree 支持以下幾種類型的查詢:
假設(shè)再 student 表中僅有:name,age,weight 這樣一個多列索引,下面的查詢都能利用到此索引
- 全值匹配
和索引列中的所有列進(jìn)行匹配。比如查詢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。
總的來看,可以發(fā)現(xiàn) B-Tree 索引適用于根據(jù)最左前綴的查找,也就是查詢字段字段順序要和索引字段順序一樣,且以第一個索引字段開頭。比如查詢name,name and age,name and age and weight都能使用索引,但是查詢age,age and name不能使用索引。
哈希索引
hash 索引基于 hash 表實(shí)現(xiàn),只有精確匹配索引所有列才會生效。MySQL 中只有 Memory 引擎顯示支持哈希索引,同時也是其默認(rèn)索引。
InnoDB 無法創(chuàng)建 hash 索引,但是它有一個功能叫自適應(yīng)hash索引,當(dāng)某些索引值使用非常頻繁時,引擎會在內(nèi)存中基于 B-Tree 索引之上再創(chuàng)建一個 hash 索引,這樣就讓 B-Tree 索引也有了一點(diǎn) hash 索引的優(yōu)點(diǎn)。這個功能是一個完全自動的、內(nèi)部的行為,也就是無法手動控制或配置。
高性能索引策略
下面是一些常見的索引策略。
獨(dú)立的列
這個很簡單,如果查詢中的列不是獨(dú)立,便無法使用索引,比如:
select * from student where age+1=12
即使 age 列有索引,上面的查詢語句也是無法利用索引的。
前綴索引和索引選擇性
如果需要索引很長的字符串列,直接創(chuàng)建索引,會讓索引占用更多的空間且速度較慢。一個優(yōu)化策略是模擬 hash 索引:給列計算一個 hash 值,并在 hash 值列建立索引。
另外一個辦法就是建立前綴索引。只索引這個字段開始的部分字符,這樣可以極大的解決空間占用,索引建立速度也會快很多。但是這樣也有如下弊端:
- 降低了索引選擇性,如果多個字符串前綴相同便無法區(qū)分,還需要進(jìn)行字符串對比。
- 不支持order by,group by,原因顯而易見,只索引了部分字符,無法完全區(qū)分。
這里的關(guān)鍵是確定索引多少個字符合適。既要避免長度過大,還要有足夠的索引選擇性。有以下兩種辦法來幫助確定索引字符數(shù):
索引字段前綴數(shù)據(jù)分布均勻。也就是以索引字符開頭的字符串?dāng)?shù)目分布均勻,比如索引 name 字段的前 3 個字符,下面的結(jié)果是比較合理(只取排名前 8 的):
數(shù)目 | 索引前三個字符 |
---|---|
500 | abc |
465 | asd |
455 | acd |
431 | zaf |
430 | aaa |
420 | vvv |
411 | asv |
512 |
如果每一列的數(shù)據(jù)都比較大,說明區(qū)分度還不高需要增大索引字符數(shù),直到這個前綴的選擇性接近完整列的索引性,也就是前面的數(shù)據(jù)要盡可能的小。
計算完整列的選擇性,并使前綴的選擇性接近于完整列的選擇性。下面語句用戶計算完整列選擇性:
-- 不同字符串的數(shù)目/總的數(shù)目就是完整列選擇性 select count(distinct name)/count(*) from person;
下面語句計算索引前 3 個字段選擇性:
-- 前3個字符不同的字符串?dāng)?shù)據(jù)/總的數(shù)據(jù) select count(distincy left(city,3))/count(*) from person
不斷增大索引字符數(shù)目,直到選擇性接近完整列選擇性且繼續(xù)增大數(shù)據(jù)選擇性提升幅度不大的時候。
創(chuàng)建方法
-- 假設(shè)最佳長度為4 alter table person add key (name(4));
多列索引
不少人有這樣的誤解,如果一個查詢用有多個字段 ‘a(chǎn)nd'查詢,那么給每個字段都建立索引不就能最大化提高效率了?事實(shí)并不是如此,mysql 只會選擇其中一個字段來進(jìn)行索引查找。這種情況下應(yīng)該建立多列索引(又叫聯(lián)合索引),就能利用多個索引字段了,注意索引列順序要和查詢的順序一致。
在 5.0 及以上版本中引入了“索引合并”的策略。一定程度上也可以使用多個單列索引,比如下面的查詢:
-- mysql會分別使用name和age索引查出數(shù)據(jù)然后合并 -- 如果使and則查出數(shù)據(jù)后再對比取交集 select * from person where name = "bob" or age=12
但是不推薦這么做,and 或 or條件過多會耗費(fèi)大量的 CPU 和內(nèi)存在算法的緩存、排序和合并操作上。
選擇合適的索引列順序
在一個多列 B-Tree 索引中,索引列的順序意味著索引首先是按照最左列進(jìn)行排序,然后是第二列…索引一個良好的多列索引應(yīng)該是將選擇性最高的索引放在最前面,然后依次降低,這樣才能更好的利于索引。選擇性計算方發(fā)見:前綴索引 小節(jié)。
聚族索引
聚族索引不是一種單獨(dú)的索引類型,而是一種數(shù)據(jù)存儲方法,具體的細(xì)節(jié)依賴其實(shí)現(xiàn)方式。
InnoDB 的聚族索引實(shí)際是在同一個結(jié)構(gòu)中保存索引值和數(shù)據(jù)行。因?yàn)椴荒芡瑫r將數(shù)據(jù)行放在兩個不同的地方,所以一個表只能有一個聚族索引。InnoDB 的聚族索引列為“主鍵列”。
如果沒有定義主鍵,InnoDB 會選擇一個唯一的非空索引代替。如果這樣的索引也沒有,InnoDB 會隱式定義一個主鍵來作為聚族索引。
聚族索引的主要優(yōu)點(diǎn)是:可以把相關(guān)數(shù)據(jù)保存在一起,減少磁盤 IO,提高查詢效率。但是也有缺點(diǎn):
- 插入順序嚴(yán)重依賴于插入順序。按照主鍵的順序插入是速度最快的方式,否則可能會導(dǎo)致頁分裂的問題出現(xiàn),會占用更多的磁盤空間,掃描速度也會變慢。可通過OPTIMIZE TABLE重新組織表。
- 更新聚族索引列代價很高,因?yàn)樗饕底兞?,行?shù)據(jù)也會跟著索引移動到新的位置上。
- 二級索引(非聚族索引)訪問行數(shù)據(jù)需要兩次索引查找,因?yàn)槎壦饕~子節(jié)點(diǎn)存儲的并不是行數(shù)據(jù)的物理位置,而是行的主鍵值,再通過主鍵值到聚族索引中取行數(shù)據(jù)。
覆蓋索引
簡單來說就是一個索引覆蓋了需要查詢的列字段,這樣就不需要再到聚族索引中利用主鍵進(jìn)行二次查找,在一個二級索引中就能取到所需的數(shù)據(jù)。
InnoDB 的索引會在葉子節(jié)點(diǎn)中保存索引值,因此如果要查詢的字段全部包含在某個索引中,且這個索引被使用了,那么就能極大的提高查詢速度。比如如下查詢語句:
-- name有索引的情況下,直接從索引的葉子節(jié)點(diǎn)中取name值返回,無需二次查找 select name from person where name = 'abc' -- 如果存在`name,age`聚合索引,也會直接返回數(shù)據(jù),無需二次查找 select name,age from person where name='abc' and age=12
使用索引進(jìn)行排序
mysql 的排序操作也是可以利用索引的,只有當(dāng)索引的列順序和ORDER BY的順序完全一致,并且所有列的排序方法(正序或者倒序)也一樣時,才能夠使用索引來進(jìn)行排序。注意:排序的字段可以比對應(yīng)的索引字段少,但是順序必須一致。如下:
-- 假設(shè)有:(name,age,sex)聯(lián)合索引 -- 可使用索引排序 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
結(jié)束
本篇基于 mysql 5.5 的版本,更新的版本可能會有不一樣的策略。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
MySQL 字符串模式匹配 擴(kuò)展正則表達(dá)式模式匹配
MySQL提供標(biāo)準(zhǔn)的SQL模式匹配,以及一種基于象Unix實(shí)用程序如vi、grep和sed的擴(kuò)展正則表達(dá)式模式匹配的格式。2010-03-03Mysql 數(shù)據(jù)庫死鎖過程分析(select for update)
最近有項目需求,需要保證多臺機(jī)器不拿到相同的數(shù)據(jù),后來發(fā)現(xiàn)Mysql查詢語句使用select.. for update經(jīng)常導(dǎo)致數(shù)據(jù)庫死鎖問題,下面小編給大家介紹mysql 數(shù)據(jù)庫死鎖過程分析(select for update),對mysql數(shù)據(jù)庫死鎖問題感興趣的朋友一起學(xué)習(xí)吧2015-12-12MySQL之終端Terminal(dos界面)管理數(shù)據(jù)庫、數(shù)據(jù)表、數(shù)據(jù)的基本操作
這篇文章主要介紹了MySQL之終端(Terminal)管理數(shù)據(jù)庫、數(shù)據(jù)表、數(shù)據(jù)的基本操作,需要的朋友可以參考下2015-03-03MySQL定時執(zhí)行腳本(計劃任務(wù))命令實(shí)例
在mysql中我們可以直接進(jìn)行一些參數(shù)設(shè)置讓它成定時為我們執(zhí)行一些任務(wù)了,這個雖然可以使用windows或者linux中的計劃任務(wù)實(shí)現(xiàn),但是mysql本身也能完成2013-10-10圖文詳解Mysql使用left?join寫查詢語句執(zhí)行很慢問題的解決
最近工作中遇到一個非常奇怪的問題,mysql中有兩張表,test_info和test_do_info需要進(jìn)行LEFT?JOIN關(guān)聯(lián)查詢,下面這篇文章主要給大家介紹了關(guān)于Mysql使用left?join寫查詢語句執(zhí)行很慢問題的解決方法2023-04-04