圖文并茂地講解Mysql索引(index)
前言
本篇文章相對(duì)來(lái)說(shuō)篇幅較長(zhǎng),不是一會(huì)半會(huì)能看完的,建議您收藏起來(lái)慢慢看,關(guān)于索引的相關(guān)知識(shí)基本上都記錄全了,通過(guò)這一篇文章足以讓您的Mysql知識(shí)更上一層樓!
1. 索引概述
1.1 什么是索引?
索引(index)是幫助MySQL高效獲取數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu)(有序)。在數(shù)據(jù)之外,數(shù)據(jù)庫(kù)系統(tǒng)還維護(hù)著滿足特定查找算法的數(shù)據(jù)結(jié)構(gòu),這些數(shù)據(jù)結(jié)構(gòu)以某種方式引用(指向)數(shù)據(jù), 這樣就可以在這些數(shù)據(jù)結(jié)構(gòu)上實(shí)現(xiàn)高級(jí)查找算法,這種數(shù)據(jù)結(jié)構(gòu)就是索引。
想要學(xué)習(xí)好索引,那么就一定要掌握mysql的數(shù)據(jù)結(jié)構(gòu),其實(shí)在一提到數(shù)據(jù)結(jié)構(gòu),對(duì)于基礎(chǔ)較差的來(lái)說(shuō),有時(shí)候是非常頭疼的,不過(guò)在這里大家完全不用擔(dān)心,接下來(lái)也會(huì)重點(diǎn)講解數(shù)據(jù)結(jié)構(gòu),盡量會(huì)以白話文的形式敘述每一個(gè)數(shù)據(jù)結(jié)構(gòu)?。?!
1.2 使用索引和不使用索引的區(qū)別
在這里我們主要演示不使用索引和使用索引的區(qū)別到底有多大。
表結(jié)構(gòu)及其數(shù)據(jù)如下:
假如我們要執(zhí)行的SQL語(yǔ)句為 : select * from user where age = 45;
(1)無(wú)索引情況
在無(wú)索引情況下,就需要從第一行開(kāi)始掃描,一直掃描到最后一行,我們稱之為 全表掃描,性能很低??赡苡械娜嗽撜f(shuō)了,明明在id為7的數(shù)據(jù)已經(jīng)找到age為45的數(shù)據(jù),為什么還是全表掃描呢?
因?yàn)閷?duì)于mysql當(dāng)中他并不知道后面是否還存在age為45的數(shù)據(jù),所以他會(huì)不落下任何一條數(shù)據(jù)!
(2)有索引情況
如果我們針對(duì)于這張表的age字段建立了索引,假設(shè)索引結(jié)構(gòu)就是二叉樹(shù),那么也就意味著,會(huì)對(duì)age這個(gè)字段建立一個(gè)二叉樹(shù)的索引結(jié)構(gòu)。而這個(gè)二叉樹(shù)當(dāng)中每個(gè)節(jié)點(diǎn)存儲(chǔ)了真正數(shù)據(jù)的位置,我們只要在樹(shù)當(dāng)中找到了對(duì)應(yīng)的age就意味著找到了真正的數(shù)據(jù)!
如下圖:當(dāng)查找age為45的時(shí)候,這時(shí)候會(huì)從根節(jié)點(diǎn)開(kāi)始判斷,根節(jié)點(diǎn)為36,比36大所以開(kāi)始走右邊的節(jié)點(diǎn),光這一下子直接排除掉樹(shù)的左邊數(shù)據(jù),然后又進(jìn)行判斷比48小,這時(shí)候走左邊節(jié)點(diǎn),然后就找到了,只需要掃描三次就可以找到數(shù)據(jù)了,極大的提高的查詢的效率。
不管是二叉樹(shù)還是B+樹(shù),一定都是有順序的,他都是在新增數(shù)據(jù)的時(shí)候,根據(jù)數(shù)據(jù)的大小進(jìn)行了排序然后分叉。也正因?yàn)槿绱?,所以提高了查詢速度?/p>
備注: 這里我們只是假設(shè)索引的結(jié)構(gòu)是二叉樹(shù),介紹一下索引的大概原理,只是一個(gè)示意圖,并不是索引的真實(shí)結(jié)構(gòu),索引的真實(shí)結(jié)構(gòu),后面會(huì)詳細(xì)介紹。
1.3 索引的特點(diǎn)
降低數(shù)據(jù)庫(kù)的IO,什么是IO?
IO就是所謂的流,流又分為了讀和寫,當(dāng)我們想要從文件當(dāng)中找數(shù)據(jù)就需要讀,當(dāng)需要修改文件的時(shí)候就需要寫,Mysql最終存儲(chǔ)的數(shù)據(jù)都是在磁盤文件當(dāng)中,那么我們想要找一條數(shù)據(jù),怎么辦呢?
先想想我們現(xiàn)實(shí)當(dāng)中想要在一個(gè)文件找有沒(méi)有哪個(gè)數(shù)據(jù)是怎么找的呢,直接打開(kāi)文件,然后全局搜索,假如文件比較大的話,搜索也會(huì)有點(diǎn)卡頓。mysql他跟我們可不一樣,我們那屬于是人家windows系統(tǒng)給我們提供了這種便捷,我們可以直接打開(kāi)文件,然后進(jìn)行搜索。
mysql假如是全表掃描,首先需要從數(shù)據(jù)文件當(dāng)中 將這張表的數(shù)據(jù)給全部讀取到內(nèi)存,然后再進(jìn)行判斷哪個(gè)數(shù)據(jù)是符合條件的。其中這也考驗(yàn)到了我們電腦的讀的能力,當(dāng)然越高配置的電腦讀取速度越快。
假如加了索引,我們只需要將索引給讀取出來(lái),因?yàn)樗饕赶蛄藬?shù)據(jù)在文件上的地址。所以只需要找到對(duì)應(yīng)數(shù)據(jù)的索引,然后通過(guò)索引獲取到數(shù)據(jù)的位置,再?gòu)臄?shù)據(jù)文件當(dāng)中將這條數(shù)據(jù)給讀取出來(lái)即可,也因此降低了IO成本。
如果數(shù)據(jù)集都讀取到內(nèi)存,假如電腦內(nèi)存只有16G,而這張表有200G,一旦全表掃描,電腦豈不是直接掛掉了?
實(shí)際上,服務(wù)端并不需要保存一個(gè)完整的結(jié)果集。取數(shù)據(jù)和發(fā)數(shù)據(jù)的流程是這樣的:
- 獲取一行,寫到 net_buffer 中。這塊內(nèi)存的大小是由參數(shù) net_buffer_length 定義的,默認(rèn)是 16k。
- 重復(fù)獲取行,直到 net_buffer 寫滿,調(diào)用網(wǎng)絡(luò)接口發(fā)出去。
- 如果發(fā)送成功,就清空 net_buffer,然后繼續(xù)取下一行,并寫入 net_buffer。
- 如果發(fā)送函數(shù)返回 EAGAIN 或 WSAEWOULDBLOCK,就表示本地網(wǎng)絡(luò)棧(socket send buffer)寫滿了,進(jìn)入等待。直到網(wǎng)絡(luò)棧重新可寫,再繼續(xù)發(fā)送。
所以我們?cè)谑褂眠^(guò)程,基本上不可能會(huì)因?yàn)閙ysql查詢數(shù)據(jù)而導(dǎo)致服務(wù)器內(nèi)存爆滿,mysql主要是占用我們服務(wù)器的IO。
2. 索引結(jié)構(gòu)
2.1 概述
MySQL的索引是在存儲(chǔ)引擎層實(shí)現(xiàn)的,不同的存儲(chǔ)引擎有不同的索引結(jié)構(gòu),主要包含以下幾種:
上述是MySQL中所支持的所有的索引結(jié)構(gòu),接下來(lái),我們?cè)賮?lái)看看不同的存儲(chǔ)引擎對(duì)于索引結(jié)構(gòu)的支持情況。
注意: 實(shí)際開(kāi)發(fā)當(dāng)中會(huì)重點(diǎn)使用B+Tree,所以本篇我們也會(huì)重點(diǎn)講解B+Tree的存儲(chǔ)結(jié)構(gòu)!我們平常所說(shuō)的索引,如果沒(méi)有特別指明,都是指B+Tree結(jié)構(gòu)組織的索引。
2.2 二叉樹(shù)
假如說(shuō)MySQL的索引結(jié)構(gòu)采用二叉樹(shù)的數(shù)據(jù)結(jié)構(gòu),比較理想的結(jié)構(gòu)如下:
如果主鍵是順序插入的,則會(huì)形成一個(gè)單向鏈表,結(jié)構(gòu)如下:
所謂的順序就是恰好每次插入的都比上個(gè)節(jié)點(diǎn)小,或者大,這樣就會(huì)形成一個(gè)鏈表
所以,如果選擇二叉樹(shù)作為索引結(jié)構(gòu),會(huì)存在以下缺點(diǎn):
- 順序插入時(shí),會(huì)形成一個(gè)鏈表,查詢性能大大降低。
- 大數(shù)據(jù)量情況下,層級(jí)較深,檢索速度慢。
此時(shí)大家可能會(huì)想到,我們可以選擇紅黑樹(shù),紅黑樹(shù)是一顆自平衡二叉樹(shù),那這樣即使是順序插入數(shù)據(jù),最終形成的數(shù)據(jù)結(jié)構(gòu)也是一顆平衡的二叉樹(shù),結(jié)構(gòu)如下:
但是,即使如此,由于紅黑樹(shù)也是一顆二叉樹(shù),所以也會(huì)存在一個(gè)缺點(diǎn):
- 大數(shù)據(jù)量情況下,層級(jí)較深,檢索速度慢。
所以,在MySQL的索引結(jié)構(gòu)中,并沒(méi)有選擇二叉樹(shù)或者紅黑樹(shù),而選擇的是B+Tree,那么什么是B+Tree呢?在詳解B+Tree之前,先來(lái)介紹一個(gè)B-Tree。
2.3 B-Tree
在說(shuō)B+Tree之前,我們先了解一下B-Tree,B-Tree又被稱之為B樹(shù),而B(niǎo)+Tree是B-Tree的變種,B樹(shù)是一種多叉路平衡查找樹(shù),相對(duì)于二叉樹(shù),B樹(shù)每個(gè)節(jié)點(diǎn)可以有多個(gè)分支,即多叉。
以一顆最大度數(shù)(max-degree)為5(5階)的b-tree為例,那這個(gè)B樹(shù)每個(gè)節(jié)點(diǎn)最多存儲(chǔ)4個(gè)key,5個(gè)指針,指針永遠(yuǎn)比key最多多1個(gè):
知識(shí)小貼士: 樹(shù)的度數(shù)指的是一個(gè)節(jié)點(diǎn)的子節(jié)點(diǎn)個(gè)數(shù)。
我們可以通過(guò)一個(gè)數(shù)據(jù)結(jié)構(gòu)可視化的網(wǎng)站來(lái)簡(jiǎn)單演示一下。https://www.cs.usfca.edu/~galles/visualization/BTree.html
插入一組數(shù)據(jù): 100 65 169 368 900 556 780 35 215 1200 234 888 158 90 1000 88 120 268 250 。然后觀察一些數(shù)據(jù)插入過(guò)程中,節(jié)點(diǎn)的變化情況。
插入數(shù)據(jù)過(guò)程一:由于設(shè)置的為五階,五階最多存儲(chǔ)4個(gè)key,5個(gè)指針,一旦節(jié)點(diǎn)存儲(chǔ)的key數(shù)量到達(dá)5,就會(huì)裂變。
插入數(shù)據(jù)過(guò)程二:直接進(jìn)行了裂變,中間元素向上分裂
插入數(shù)據(jù)過(guò)程三:
插入數(shù)據(jù)過(guò)程四:這時(shí)候會(huì)發(fā)現(xiàn)556放到了右邊節(jié)點(diǎn)的中間位置,因?yàn)锽-TREE是有序的
如下是最終結(jié)果,后面的我就不再演示了,強(qiáng)烈建議大家自己去網(wǎng)站插入看一下,這樣可以更好的熟悉是數(shù)據(jù)結(jié)構(gòu)!
B-Tree特點(diǎn):
- 5階的B樹(shù),每一個(gè)節(jié)點(diǎn)最多存儲(chǔ)4個(gè)key,對(duì)應(yīng)5個(gè)指針。
- 一旦節(jié)點(diǎn)存儲(chǔ)的key數(shù)量到達(dá)5,就會(huì)裂變,中間元素向上分裂。
- 在B樹(shù)中,非葉子節(jié)點(diǎn)和葉子節(jié)點(diǎn)都會(huì)存放數(shù)據(jù)。
2.4 B+Tree
B+Tree是B-Tree的變種,我們以一顆最大度數(shù)(max-degree)為4(4階)的b+tree為例,來(lái)看一下其結(jié)構(gòu)示意圖:
我們可以看到,兩部分:
- 綠色框框起來(lái)的部分,是索引部分,僅僅起到索引數(shù)據(jù)的作用,不存儲(chǔ)數(shù)據(jù)。
- 紅色框框起來(lái)的部分,是數(shù)據(jù)存儲(chǔ)部分,在其葉子節(jié)點(diǎn)中要存儲(chǔ)具體的數(shù)據(jù)。
我們可以通過(guò)一個(gè)數(shù)據(jù)結(jié)構(gòu)可視化的網(wǎng)站來(lái)簡(jiǎn)單演示一下。
https://www.cs.usfca.edu/~galles/visualization/BPlusTree.html
插入一組數(shù)據(jù): 100 65 169 368 900 556 780 35 215 1200 234 888 158 90 1000 88 120 268 250 。然后觀察一些數(shù)據(jù)插入過(guò)程中,節(jié)點(diǎn)的變化情況。
如下是最終插入的結(jié)果展示:
最終我們看到,B+Tree 與 B-Tree相比,主要有以下三點(diǎn)區(qū)別:
所有的數(shù)據(jù)都會(huì)出現(xiàn)在葉子節(jié)點(diǎn)。葉子節(jié)點(diǎn)形成一個(gè)單向鏈表。非葉子節(jié)點(diǎn)僅僅起到索引數(shù)據(jù)作用,具體的數(shù)據(jù)都是在葉子節(jié)點(diǎn)存放的。
上述我們所看到的結(jié)構(gòu)是標(biāo)準(zhǔn)的B+Tree的數(shù)據(jù)結(jié)構(gòu),接下來(lái),我們?cè)賮?lái)看看MySQL中優(yōu)化之后的B+Tree。
- 所有的數(shù)據(jù)都會(huì)出現(xiàn)在葉子節(jié)點(diǎn)。
- 葉子節(jié)點(diǎn)形成一個(gè)單向鏈表。
- 非葉子節(jié)點(diǎn)僅僅起到索引數(shù)據(jù)作用,具體的數(shù)據(jù)都是在葉子節(jié)點(diǎn)存放的。
mysql當(dāng)中一頁(yè)代表了B+TREE數(shù)據(jù)結(jié)構(gòu)當(dāng)中的一個(gè)葉子節(jié)點(diǎn),并且一頁(yè)固定大小為16kb。
總結(jié):mysql的B+Tree數(shù)據(jù)結(jié)構(gòu),就是在原來(lái)的B+Tree結(jié)構(gòu)基礎(chǔ)上,將葉子節(jié)點(diǎn)的單向鏈表改為了雙向鏈表
2.5 Hash
MySQL中除了支持B+Tree索引,還支持一種索引類型—Hash索引。
(1) 結(jié)構(gòu)
哈希索引就是采用一定的hash算法,將鍵值換算成新的hash值,映射到對(duì)應(yīng)的槽位上,然后存儲(chǔ)在hash表中。
以下圖為例:將name設(shè)置為hash索引,假如想要找對(duì)應(yīng)的name數(shù)據(jù),首先會(huì)對(duì)name進(jìn)行hash計(jì)算得出一個(gè)下標(biāo)值,通過(guò)下標(biāo)值來(lái)獲取到對(duì)應(yīng)的數(shù)據(jù)。
如果兩個(gè)(或多個(gè))鍵值,映射到一個(gè)相同的槽位上,他們就產(chǎn)生了hash沖突(也稱為hash碰撞),可以通過(guò)鏈表來(lái)解決。
(2) 特點(diǎn)
- Hash索引只能用于對(duì)等比較(=,in),不支持范圍查詢(between,>,< ,…)
- 無(wú)法利用索引完成排序操作
- 查詢效率高,通常(不存在hash沖突的情況)只需要一次檢索就可以了,效率通常要高于B+tree索 引
(3) 存儲(chǔ)引擎支持
在MySQL中,支持hash索引的是Memory存儲(chǔ)引擎。 而InnoDB中具有自適應(yīng)hash功能,hash索引是InnoDB存儲(chǔ)引擎根據(jù)B+Tree索引在指定條件下自動(dòng)構(gòu)建的。
思考題: 為什么InnoDB存儲(chǔ)引擎選擇使用B+tree索引結(jié)構(gòu)?
- 相對(duì)于二叉樹(shù),層級(jí)更少,搜索效率高;
- 對(duì)于B-tree,無(wú)論是葉子節(jié)點(diǎn)還是非葉子節(jié)點(diǎn),都會(huì)保存數(shù)據(jù),這樣導(dǎo)致一頁(yè)中存儲(chǔ)的鍵值減少,指針跟著減少,要同樣保存大量數(shù)據(jù),只能增加樹(shù)的高度,導(dǎo)致性能降低;
- 相對(duì)Hash索引,B+tree支持范圍匹配及排序操作;
3.索引分類
3.1 索引分類
在MySQL數(shù)據(jù)庫(kù),將索引的具體類型主要分為以下幾類:主鍵索引、唯一索引、常規(guī)索引、全文索引。
其實(shí)索引結(jié)構(gòu)就是索引類型分類,他兩本質(zhì)就是一個(gè)東西,沒(méi)有區(qū)別!
索引分類,也可以在Navcat客戶端當(dāng)中,查看可選擇的索引類型!
3.2 聚集索引&二級(jí)索引
而在在InnoDB存儲(chǔ)引擎中,根據(jù)索引的存儲(chǔ)形式,又可以分為以下兩種:
聚集索引和二級(jí)索引跟我們上面說(shuō)的索引類型可不是一個(gè)東西,切勿混淆了!
聚集索引選取規(guī)則:
- 如果存在主鍵,主鍵索引就是聚集索引。
- 如果不存在主鍵,將使用第一個(gè)唯一(UNIQUE)索引作為聚集索引。
- 如果表沒(méi)有主鍵,或沒(méi)有合適的唯一索引,則InnoDB會(huì)自動(dòng)生成一個(gè)rowid作為隱藏的聚集索 引。
基于B+Tree的索引類型,聚集索引和二級(jí)索引的具體結(jié)構(gòu)如下:
假如有一張表,表里分別有三個(gè)字段id、name、gender,然后id為主鍵,那么他就是聚集索引,然后我們又給name也添加了一個(gè)索引,那么他就被稱為二級(jí)索引。
- 聚集索引的葉子節(jié)點(diǎn)下掛的是這一行的數(shù)據(jù) 。
- 二級(jí)索引的葉子節(jié)點(diǎn)下掛的是該字段值對(duì)應(yīng)的主鍵值。
接下來(lái),我們來(lái)分析一下,當(dāng)我們執(zhí)行如下的SQL語(yǔ)句時(shí),具體的查找過(guò)程是什么樣子的。
具體過(guò)程如下:
- 由于是根據(jù)name字段進(jìn)行查詢,所以先根據(jù)name='Arm’到name字段的二級(jí)索引中進(jìn)行匹配查找。但是在二級(jí)索引中只能查找到 Arm 對(duì)應(yīng)的主鍵值 10。
- 由于查詢返回的數(shù)據(jù)是*,所以此時(shí),還需要根據(jù)主鍵值10,到聚集索引中查找10對(duì)應(yīng)的記錄,最終找到10對(duì)應(yīng)的行row。
- 最終拿到這一行的數(shù)據(jù),直接返回即可。
回表查詢: 這種先到二級(jí)索引中查找數(shù)據(jù),找到主鍵值,然后再到聚集索引中根據(jù)主鍵值,獲取數(shù)據(jù)的方式,就稱之為回表查詢。
思考題:
(1)以下兩條SQL語(yǔ)句,那個(gè)執(zhí)行效率高? 為什么?
- select * from user where id = 10 ;
- select * from user where name = ‘Arm’ ;
備注: id為主鍵,name字段創(chuàng)建的有索引;
解答: A 語(yǔ)句的執(zhí)行性能要高于B 語(yǔ)句。
因?yàn)锳語(yǔ)句直接走聚集索引,直接返回?cái)?shù)據(jù)。 而B(niǎo)語(yǔ)句需要先查詢name字段的二級(jí)索引,然后再查詢聚集索引,也就是需要進(jìn)行回表查詢。
(2)InnoDB主鍵索引的B+tree高度為多高呢?
關(guān)于頁(yè)相關(guān)知識(shí)不是很了解的,建議看看mysql存儲(chǔ)引擎篇:http://www.dbjr.com.cn/article/257845.htm
一頁(yè)最大為16KB,假設(shè)一行數(shù)據(jù)大小為1k,則一頁(yè)中可以存儲(chǔ)16行這樣的數(shù)據(jù)。InnoDB的指針占用6個(gè)字節(jié)的空間,主鍵假設(shè)為bigint類型,那么就是占用字節(jié)數(shù)為8。指針的數(shù)量是鍵值數(shù)量+1。
高度為2:
- 第一步:非葉子節(jié)點(diǎn)是不存儲(chǔ)數(shù)據(jù)的,那么我們可以通過(guò)已知的現(xiàn)有條件來(lái)算出葉子節(jié)點(diǎn)可以存儲(chǔ)多少key值。 n * 8 + (n + 1) * 6 = 16*1024 ,算出n約為 1170(這里的n代表的就是key值,8代表的是假設(shè)id為8字節(jié),n+1代表的是指針,6代表的是指針占用6字節(jié),16*1024代表的是一頁(yè)最大為16kb*1024字節(jié)數(shù))
- 第二步:有了key值數(shù)量后,通過(guò)key+1得出指針數(shù)量,指針數(shù)量就代表著最多有多少頁(yè),因?yàn)楸旧砀叨葹?,然后乘于頁(yè)大小,得出最大的空間為18736:1171* 16 = 18736
- 也就是說(shuō),如果樹(shù)的高度為2,假設(shè)一條數(shù)據(jù)為1kb,則可以存儲(chǔ) 18000 多條記錄。
高度為3:
- 1171 * 1171 * 16 = 21939856
- 有多少指針就代表有多少頁(yè),因?yàn)槲覀円蟮氖乔蟪鲎畲髷?shù)據(jù)量,所以一個(gè)指針肯定對(duì)應(yīng)一個(gè)頁(yè)
- 也就是說(shuō),如果樹(shù)的高度為3,則可以存儲(chǔ) 2200w 左右的記錄。
4. 索引語(yǔ)法
創(chuàng)建索引:CREATE [ UNIQUE | FULLTEXT ] INDEX index_name ON table_name ( index_col_name,... ) ;
查看索引:SHOW INDEX FROM table_name ;
刪除索引:DROP INDEX index_name ON table_name ;
案例演示: 先來(lái)創(chuàng)建一張表 tb_user,并且查詢測(cè)試數(shù)據(jù)。
CREATE TABLE tb_user ( id INT PRIMARY KEY auto_increment COMMENT '主鍵', NAME VARCHAR ( 50 ) NOT NULL COMMENT '用戶名', phone VARCHAR ( 11 ) NOT NULL COMMENT '手機(jī)號(hào)', email VARCHAR ( 100 ) COMMENT '郵箱', profession VARCHAR ( 11 ) COMMENT '專業(yè)', age TINYINT UNSIGNED COMMENT '年齡', gender CHAR ( 1 ) COMMENT '性別 , 1: 男, 2: 女', STATUS CHAR ( 1 ) COMMENT '狀態(tài)', createtime datetime COMMENT '創(chuàng)建時(shí)間' ) COMMENT '系統(tǒng)用戶表'; INSERT INTO tb_user ( NAME, phone, email, profession, age, gender, STATUS, createtime ) VALUES ( '呂布', '17799990000', 'lvbu666@163.com', '軟件工程', 23, '1', '6', '2001-02-02 00:00:00' ); INSERT INTO tb_user ( NAME, phone, email, profession, age, gender, STATUS, createtime ) VALUES ( '曹操', '17799990001', 'caocao666@qq.com', '通訊工程', 33, '1', '0', '2001-03-05 00:00:00' ); INSERT INTO tb_user ( NAME, phone, email, profession, age, gender, STATUS, createtime ) VALUES ( '趙云', '17799990002', '17799990@139.com', '英語(yǔ)', 34, '1', '2', '2002-03-02 00:00:00' ); INSERT INTO tb_user ( NAME, phone, email, profession, age, gender, STATUS, createtime ) VALUES ( '孫悟空', '17799990003', '17799990@sina.com', '工程造價(jià)', 54, '1', '0', '2001-07-02 00:00:00' ); INSERT INTO tb_user ( NAME, phone, email, profession, age, gender, STATUS, createtime ) VALUES ( '花木蘭', '17799990004', '19980729@sina.com', '軟件工程', 23, '2', '1', '2001-04-22 00:00:00' ); INSERT INTO tb_user ( NAME, phone, email, profession, age, gender, STATUS, createtime ) VALUES ( '大喬', '17799990005', 'daqiao666@sina.com', '舞蹈', 22, '2', '0', '2001-02-07 00:00:00' ); INSERT INTO tb_user ( NAME, phone, email, profession, age, gender, STATUS, createtime ) VALUES ( '露娜', '17799990006', 'luna_love@sina.com', '應(yīng)用數(shù)學(xué)', 24, '2', '0', '2001-02-08 00:00:00' ); INSERT INTO tb_user ( NAME, phone, email, profession, age, gender, STATUS, createtime ) VALUES ( '程咬金', '17799990007', 'chengyaojin@163.com', '化工', 38, '1', '5', '2001-05-23 00:00:00' ); INSERT INTO tb_user ( NAME, phone, email, profession, age, gender, STATUS, createtime ) VALUES ( '項(xiàng)羽', '17799990008', 'xiaoyu666@qq.com', '金屬材料', 43, '1', '0', '2001-09-18 00:00:00' ); INSERT INTO tb_user ( NAME, phone, email, profession, age, gender, STATUS, createtime ) VALUES ( '白起', '17799990009', 'baiqi666@sina.com', '機(jī)械工程及其自動(dòng) 化', 27, '1', '2', '2001-08-16 00:00:00' ); INSERT INTO tb_user ( NAME, phone, email, profession, age, gender, STATUS, createtime ) VALUES ( '韓信', '17799990010', 'hanxin520@163.com', '無(wú)機(jī)非金屬材料工 程', 27, '1', '0', '2001-06-12 00:00:00' ); INSERT INTO tb_user ( NAME, phone, email, profession, age, gender, STATUS, createtime ) VALUES ( '荊軻', '17799990011', 'jingke123@163.com', '會(huì)計(jì)', 29, '1', '0', '2001-05-11 00:00:00' ); INSERT INTO tb_user ( NAME, phone, email, profession, age, gender, STATUS, createtime ) VALUES ( '蘭陵王', '17799990012', 'lanlinwang666@126.com', '工程造價(jià)', 44, '1', '1', '2001-04-09 00:00:00' ); INSERT INTO tb_user ( NAME, phone, email, profession, age, gender, STATUS, createtime ) VALUES ( '狂鐵', '17799990013', 'kuangtie@sina.com', '應(yīng)用數(shù)學(xué)', 43, '1', '2', '2001-04-10 00:00:00' ); INSERT INTO tb_user ( NAME, phone, email, profession, age, gender, STATUS, createtime ) VALUES ( '貂蟬', '17799990014', '84958948374@qq.com', '軟件工程', 40, '2', '3', '2001-02-12 00:00:00' ); INSERT INTO tb_user ( NAME, phone, email, profession, age, gender, STATUS, createtime ) VALUES ( '妲己', '17799990015', '2783238293@qq.com', '軟件工程', 31, '2', '0', '2001-01-30 00:00:00' ); INSERT INTO tb_user ( NAME, phone, email, profession, age, gender, STATUS, createtime ) VALUES ( '羋月', '17799990016', 'xiaomin2001@sina.com', '工業(yè)經(jīng)濟(jì)', 35, '2', '0', '2000-05-03 00:00:00' ); INSERT INTO tb_user ( NAME, phone, email, profession, age, gender, STATUS, createtime ) VALUES ( '嬴政', '17799990017', '8839434342@qq.com', '化工', 38, '1', '1', '2001-08-08 00:00:00' ); INSERT INTO tb_user ( NAME, phone, email, profession, age, gender, STATUS, createtime ) VALUES ( '狄仁杰', '17799990018', 'jujiamlm8166@163.com', '國(guó)際貿(mào)易', 30, '1', '0', '2007-03-12 00:00:00' ); INSERT INTO tb_user ( NAME, phone, email, profession, age, gender, STATUS, createtime ) VALUES ( '安琪拉', '17799990019', 'jdodm1h@126.com', '城市規(guī)劃', 51, '2', '0', '2001-08-15 00:00:00' ); INSERT INTO tb_user ( NAME, phone, email, profession, age, gender, STATUS, createtime ) VALUES ( '典韋', '17799990020', 'ycaunanjian@163.com', '城市規(guī)劃', 52, '1', '2', '2000-04-12 00:00:00' ); INSERT INTO tb_user ( NAME, phone, email, profession, age, gender, STATUS, createtime ) VALUES ( '廉頗', '17799990021', 'lianpo321@126.com', '土木工程', 19, '1', '3', '2002-07-18 00:00:00' ); INSERT INTO tb_user ( NAME, phone, email, profession, age, gender, STATUS, createtime ) VALUES ( '后羿', '17799990022', 'altycj2000@139.com', '城市園林', 20, '1', '0', '2002-03-10 00:00:00' ); INSERT INTO tb_user ( NAME, phone, email, profession, age, gender, STATUS, createtime ) VALUES ( '姜子牙', '17799990023', '37483844@qq.com', '工程造價(jià)', 29, '1', '4', '2003-05-26 00:00:00' );
數(shù)據(jù)準(zhǔn)備好了之后,接下來(lái),我們就來(lái)完成如下需求:
- name字段為姓名字段,該字段的值可能會(huì)重復(fù),為該字段創(chuàng)建索引:CREATE INDEX idx_user_name ON tb_user(name);
- phone手機(jī)號(hào)字段的值,是非空,且唯一的,為該字段創(chuàng)建唯一索引:CREATE UNIQUE INDEX idx_user_phone ON tb_user(phone);
- 為profession、age、status創(chuàng)建聯(lián)合索引(創(chuàng)建的索引同時(shí)指定了多個(gè)列,我們稱之為聯(lián)合索引或者組合索引):CREATE INDEX idx_user_pro_age_sta ON tb_user(profession,age,status);
- 為email建立合適的索引來(lái)提升查詢效率:CREATE INDEX idx_email ON tb_user(email);
完成上述的需求之后,我們?cè)俨榭磘b_user表的所有的索引數(shù)據(jù):
5. SQL性能分析
5.1 SQL執(zhí)行頻率
MySQL 客戶端連接成功后,通過(guò) show [session|global] status
命令可以提供服務(wù)器狀態(tài)信息。通過(guò)如下指令,可以查看當(dāng)前數(shù)據(jù)庫(kù)的INSERT、UPDATE、DELETE、SELECT的訪問(wèn)頻次:
-- session 是查看當(dāng)前會(huì)話 ; -- global 是查詢?nèi)謹(jǐn)?shù)據(jù) ; SHOW GLOBAL STATUS LIKE 'Com_______';
- Com_delete: 刪除次數(shù)
- Com_insert: 插入次數(shù)
- Com_select: 查詢次數(shù)
- Com_update: 更新次數(shù)
我們可以在當(dāng)前數(shù)據(jù)庫(kù)再執(zhí)行幾次查詢操作,然后再次查看執(zhí)行頻次,看看 Com_select 參數(shù)會(huì)不會(huì)變化。
通過(guò)上述指令,我們可以查看到當(dāng)前數(shù)據(jù)庫(kù)到底是以查詢?yōu)橹?,還是以增刪改為主,從而為數(shù)據(jù)庫(kù)優(yōu)化提供參考依據(jù)。 如果是以增刪改為主,我們可以考慮不對(duì)其進(jìn)行索引的優(yōu)化。 如果是以查詢?yōu)橹?,那么就要考慮對(duì)數(shù)據(jù)庫(kù)的索引進(jìn)行優(yōu)化了。
那么通過(guò)查詢SQL的執(zhí)行頻次,我們就能夠知道當(dāng)前數(shù)據(jù)庫(kù)到底是增刪改為主,還是查詢?yōu)橹鳌?那假如說(shuō)是以查詢?yōu)橹?,我們又該如何定位針?duì)于那些查詢語(yǔ)句進(jìn)行優(yōu)化呢? 次數(shù)我們可以借助于慢查詢?nèi)罩尽?/p>
接下來(lái),我們就來(lái)介紹一下MySQL中的慢查詢?nèi)罩尽?/strong>
5.2 慢查詢?nèi)罩?/h3>
慢查詢?nèi)罩居涗浟怂袌?zhí)行時(shí)間超過(guò)指定參數(shù)(long_query_time,單位:秒,默認(rèn)10秒)的所有SQL語(yǔ)句的日志。
MySQL的慢查詢?nèi)罩灸J(rèn)沒(méi)有開(kāi)啟,我們可以查看一下系統(tǒng)變量 slow_query_log
show variables like 'slow_query_log';
如果要開(kāi)啟慢查詢?nèi)罩?,需要在MySQL的配置文件(如果是linux則是/etc/my.cnf,如果是windows在mysql安裝目錄下的my.ini)中配置如下信息:注意配置一定要加載配置文件最后面,不然可能會(huì)出現(xiàn)問(wèn)題!
這里我用的是mysql5.5版本,然后是在windows下配置的以下參數(shù),并沒(méi)有出現(xiàn)問(wèn)題,如果您出現(xiàn)了問(wèn)題,那么可能mysql版本之間變量名稱有變化!
ft_min_word_len=1 #慢查詢?nèi)罩敬娣怕窂降刂? log-slow-queries = D:\slow.log #慢查詢記錄的時(shí)間 long_query_time = 2
測(cè)試: 配置完畢之后,重新啟動(dòng)MySQL服務(wù)器進(jìn)行測(cè)試,查看慢日志文件中記錄的信息。
mysql快速生成千萬(wàn)數(shù)據(jù):http://www.dbjr.com.cn/article/257848.htm
(1)執(zhí)行如下SQL語(yǔ)句 :
-- 這條SQL執(zhí)行效率比較高, 執(zhí)行耗時(shí) 0.00sec select * from tb_user; -- 由于t_user表中, 預(yù)先存入了700w的記錄, count一次,耗時(shí)6s select count(*) from t_user;
(2)檢查慢查詢?nèi)罩?/strong>
最終我們發(fā)現(xiàn),在慢查詢?nèi)罩局?,只?huì)記錄執(zhí)行時(shí)間超多我們預(yù)設(shè)時(shí)間(2s)的SQL,執(zhí)行較快的SQL是不會(huì)記錄的。
那這樣,通過(guò)慢查詢?nèi)罩?,就可以定位出?zhí)行效率比較低的SQL,從而有針對(duì)性的進(jìn)行優(yōu)化。
5.3 profile詳情
show profiles 能夠在做SQL優(yōu)化時(shí)幫助我們了解時(shí)間都耗費(fèi)到哪里去了。通過(guò)have_profiling參數(shù),能夠看到當(dāng)前MySQL是否支持profile操作:SELECT @@have_profiling ;
profile查詢出來(lái)的數(shù)據(jù)每個(gè)客戶端是相互隔離的。
查看prifile是否開(kāi)啟了:select @@profiling;
可以看到,當(dāng)前MySQL是支持 profile操作的,但是開(kāi)關(guān)是關(guān)閉的??梢酝ㄟ^(guò)set語(yǔ)句在session/global級(jí)別開(kāi)啟profiling:
SET profiling = 1;
開(kāi)關(guān)已經(jīng)打開(kāi)了,接下來(lái),我們所執(zhí)行的SQL語(yǔ)句,都會(huì)被MySQL記錄,并記錄執(zhí)行時(shí)間消耗到哪兒去了。 我們直接執(zhí)行如下的SQL語(yǔ)句:
select * from tb_user; select * from tb_user where id = 1; select * from tb_user where name = '白起'; select count(*) from t_user;
執(zhí)行一系列的業(yè)務(wù)SQL的操作,然后通過(guò)如下指令查看指令的執(zhí)行耗時(shí):
- 查看每一條SQL的耗時(shí)基本情況:show profiles;
- 查看指定query_id的SQL語(yǔ)句各個(gè)階段的耗時(shí)情況:show profile for query query_id;
- 查看指定query_id的SQL語(yǔ)句CPU的使用情況:show profile cpu for query query_id;
查看每一條SQL的耗時(shí)情況:
查看指定SQL各個(gè)階段的耗時(shí)情況 :
5.4 explain
EXPLAIN 或者 DESC命令獲取 MySQL 如何執(zhí)行 SELECT 語(yǔ)句的信息,包括在 SELECT 語(yǔ)句執(zhí)行過(guò)程中表如何連接和連接的順序。
語(yǔ)法:
-- 直接在select語(yǔ)句之前加上關(guān)鍵字 explain / desc EXPLAIN SELECT 字段列表 FROM 表名 WHERE 條件 ;
Explain 執(zhí)行計(jì)劃中各個(gè)字段的含義:
6. 索引使用
6.1 驗(yàn)證索引效率
在講解索引的使用原則之前,先通過(guò)一個(gè)簡(jiǎn)單的案例,來(lái)驗(yàn)證一下索引,看看是否能夠通過(guò)索引來(lái)提升數(shù)據(jù)查詢性能。在演示的時(shí)候,我們還是使用之前準(zhǔn)備的一張表 t_user, 在這張表中準(zhǔn)備了700w的記錄。
這張表中id為主鍵,有主鍵索引,而其他字段是沒(méi)有建立索引的。 我們先來(lái)查詢其中的一條記錄,看看里面的字段情況,執(zhí)行如下SQL:
可以看到即使有1000w的數(shù)據(jù),根據(jù)id進(jìn)行數(shù)據(jù)查詢,性能依然很快,因?yàn)橹麈Iid是有索引的。 那么接下來(lái),我們?cè)賮?lái)根據(jù) c_name字段進(jìn)行查詢,執(zhí)行如下SQL:
我們可以看到根據(jù)c_name字段進(jìn)行查詢,查詢返回了一條數(shù)據(jù),結(jié)果耗時(shí) 3.52 sec,就是因?yàn)閏_name沒(méi)有索引,而造成查詢效率很低。
那么我們可以針對(duì)于c_name字段,建立一個(gè)索引,建立了索引之后,我們?cè)俅胃鶕?jù)c_name進(jìn)行查詢,再來(lái)看一下查詢耗時(shí)情況。
創(chuàng)建索引:create index idx_user_cname on t_user(c_name);
然后再次執(zhí)行相同的SQL語(yǔ)句,再次查看SQL的耗時(shí)。
我們明顯會(huì)看到,c_name字段建立了索引之后,查詢性能大大提升。建立索引前后,查詢耗時(shí)都不是一個(gè)數(shù)量級(jí)的。
6.2 最左前綴法則
如果索引了多列(聯(lián)合索引),要遵守最左前綴法則。最左前綴法則指的是查詢從索引的最左列開(kāi)始,并且不跳過(guò)索引中的列。如果跳躍某一列,索引將會(huì)部分失效(后面的字段索引失效)。
以 tb_user 表為例,我們先來(lái)查看一下之前 tb_user 表所創(chuàng)建的索引。
在 tb_user 表中,有一個(gè)聯(lián)合索引,這個(gè)聯(lián)合索引涉及到三個(gè)字段,順序分別為:profession,age,status。
對(duì)于最左前綴法則指的是,查詢時(shí),最左變的列,也就是profession必須存在,否則索引全部失效。而且中間不能跳過(guò)某一列,否則該列后面的字段索引將失效。
(1)第一步我們先演示索引成功的案例,看一下具體的執(zhí)行計(jì)劃:
explain select * from tb_user where profession = '軟件工程' and age = 31 and status = '0';
explain select * from tb_user where profession = '軟件工程' and age = 31;
explain select * from tb_user where profession = '軟件工程';
以上的這三組測(cè)試中,我們發(fā)現(xiàn)只要聯(lián)合索引最左邊的字段 profession存在,索引就會(huì)生效,只不過(guò)索引的長(zhǎng)度不同。 而且由以上三組測(cè)試,我們也可以推測(cè)出profession字段索引長(zhǎng)度為36、age字段索引長(zhǎng)度為2、status字段索引長(zhǎng)度為4。我們下面主要根據(jù)這個(gè)長(zhǎng)度來(lái)判斷組合索引當(dāng)中哪個(gè)索引沒(méi)有生效!
注意:如果看不懂explain執(zhí)行計(jì)劃的,一定要看explamin介紹的參數(shù)解釋!不同的數(shù)據(jù)庫(kù)編碼以及數(shù)據(jù)庫(kù)版本可能key_len長(zhǎng)度也會(huì)不一樣,這里我用的mysql是5.5版本,表用的是utf-8編碼
(2)下面我們進(jìn)行演示最左邊的profession列沒(méi)用到的時(shí)候,索引失效的情況:
explain select * from tb_user where age = 31 and status = '0';
explain select * from tb_user where status = '0';
而通過(guò)上面的這兩組測(cè)試,我們也可以看到索引并未生效,原因是因?yàn)椴粷M足最左前綴法則,聯(lián)合索引最左邊的列profession不存在。
(3)下面我們進(jìn)行演示跳過(guò)中間的列,索引失效的情況:
explain select * from tb_user where profession = '軟件工程' and status = '0';
上述的SQL查詢時(shí),存在profession字段,最左邊的列是存在的,索引滿足最左前綴法則的基本條件。但是查詢時(shí),跳過(guò)了age這個(gè)列,所以后面的列索引是不會(huì)使用的,也就是索引部分生效,所以索引的長(zhǎng)度就是36。
思考題:當(dāng)執(zhí)行SQL語(yǔ)句:
explain select * from tb_user where age = 31 and status = '0' and profession = '軟件工程';
時(shí),是否滿足最左前綴法則,走不走上述的聯(lián)合索引,索引長(zhǎng)度?
可以看到,是完全滿足最左前綴法則的,索引長(zhǎng)度42,聯(lián)合索引是生效的。
注意 : 最左前綴法則中指的最左邊的列,是指在查詢時(shí),聯(lián)合索引的最左邊的字段(即是第一個(gè)字段)必須存在,與我們編寫SQL時(shí),條件編寫的先后順序無(wú)關(guān)。
6.3 索引失效情況
6.3.1 ><范圍查詢
聯(lián)合索引中,出現(xiàn)范圍查詢(>,<),范圍查詢右側(cè)的列索引失效。
explain select * from tb_user where profession = '軟件工程' and age > 30 and status = '0';
當(dāng)范圍查詢使用> 或 < 時(shí),走聯(lián)合索引了,但是索引的長(zhǎng)度為38,就說(shuō)明范圍查詢右邊的status字段是沒(méi)有走索引的。
explain select * from tb_user where profession = '軟件工程' and age >= 30 and status = '0';
當(dāng)范圍查詢使用>= 或 <= 時(shí),走聯(lián)合索引了,但是索引的長(zhǎng)度為42,就說(shuō)明所有的字段都是走索引
的。
所以,在業(yè)務(wù)允許的情況下,盡可能的使用類似于 >= 或 <= 這類的范圍查詢,而避免使用 > 或 < 。
6.3.2 索引列運(yùn)算
不要在索引列上進(jìn)行運(yùn)算操作, 索引將失效。
在tb_user表中,除了前面介紹的聯(lián)合索引之外,還有一個(gè)索引,是phone字段的單列索引。
當(dāng)根據(jù)phone字段進(jìn)行等值匹配查詢時(shí), 索引生效。
explain select * from tb_user where phone = '17799990015';
當(dāng)根據(jù)phone字段進(jìn)行函數(shù)運(yùn)算操作之后,索引失效。
explain select * from tb_user where substring(phone,10,2) = '15';
6.3.3 字符串不加引號(hào)
字符串類型字段使用時(shí),不加引號(hào),索引將失效。
接下來(lái),我們通過(guò)兩組示例,來(lái)看看對(duì)于字符串類型的字段,加單引號(hào)與不加單引號(hào)的區(qū)別.
第一組:
explain select * from tb_user where profession = '軟件工程' and age = 31 and status = '0';
explain select * from tb_user where profession = '軟件工程' and age = 31 and status = 0;
第二組:
explain select * from tb_user where phone = '17799990015';
explain select * from tb_user where phone = 17799990015;
6.3.4 模糊查詢
如果僅僅是尾部模糊匹配,索引不會(huì)失效。如果是頭部模糊匹配,索引失效。
接下來(lái),我們來(lái)看一下這三條SQL語(yǔ)句的執(zhí)行效果,查看一下其執(zhí)行計(jì)劃:
由于下面查詢語(yǔ)句中,都是根據(jù)profession字段查詢,符合最左前綴法則,聯(lián)合索引是可以生效的,我們主要看一下,模糊查詢時(shí),%加在關(guān)鍵字之前,和加在關(guān)鍵字之后的影響。
explain select * from tb_user where profession like ‘軟件%'; explain select * from tb_user where profession like ‘%工程'; explain select * from tb_user where profession like ‘%工%';
經(jīng)過(guò)上述的測(cè)試,我們發(fā)現(xiàn),在like模糊查詢中,在關(guān)鍵字后面加%,索引可以生效。而如果在關(guān)鍵字前面加了%,索引將會(huì)失效。
6.3.5 or連接條件
用or分割開(kāi)的條件, 如果or前的條件中的列有索引,而后面的列中沒(méi)有索引,那么涉及的索引都不會(huì)被用到。
explain select * from tb_user where id = 10 or age = 23; explain select * from tb_user where phone = ‘17799990017' or age = 23;
由于age沒(méi)有索引,所以即使id、phone有索引,索引也會(huì)失效。所以需要針對(duì)于age也要建立索引。
然后,我們可以對(duì)age字段建立索引:create index idx_user_age on tb_user(age);
建立了索引之后,我們?cè)俅螆?zhí)行上述的SQL語(yǔ)句,看看前后執(zhí)行計(jì)劃的變化。
這里我發(fā)現(xiàn)一個(gè)問(wèn)題,我用的mysql5.5版本or不管兩邊是否都有索引,直接都不會(huì)走索引,但是又用了mysql 8測(cè)試了一下,.當(dāng)age沒(méi)有索引的時(shí)候不走索引,當(dāng)兩邊都有索引的時(shí)候確實(shí)會(huì)走索引。
6.3.6 數(shù)據(jù)分布影響
如果MySQL評(píng)估使用索引比全表更慢,則不使用索引。
explain select * from tb_user where phone >= '17799990005'; explain select * from tb_user where phone >= '17799990015';
mysql5.5版本執(zhí)行如下:
mysql8.0版本執(zhí)行如下:
經(jīng)過(guò)測(cè)試我們發(fā)現(xiàn),在mysql8版本當(dāng)中,相同的SQL語(yǔ)句,只是傳入的字段值不同,最終的執(zhí)行計(jì)劃完全不一樣,這是為什么呢?
就是因?yàn)镸ySQL 8 版本 在查詢時(shí),會(huì)評(píng)估使用索引的效率與走全表掃描的效率,如果走全表掃描更快,則放棄索引,走全表掃描。 因?yàn)樗饕怯脕?lái)索引少量數(shù)據(jù)的,如果通過(guò)索引查詢返回大批量的數(shù)據(jù),則還不如走全表掃描來(lái)的快,此時(shí)索引就會(huì)失效。
接下來(lái),我們?cè)賮?lái)看看 is null 與 is not null 操作是否走索引。
explain select * from tb_user where profession is null; explain select * from tb_user where profession is not null;
接下來(lái),我們做一個(gè)操作將profession字段值全部更新為null:update tb_user set profession = null;
然后,再次執(zhí)行上述的兩條SQL,查看SQL語(yǔ)句的執(zhí)行計(jì)劃。
最終我們看到,一模一樣的SQL語(yǔ)句,先后執(zhí)行了兩次,結(jié)果查詢計(jì)劃是不一樣的,為什么會(huì)出現(xiàn)這種現(xiàn)象,這是和數(shù)據(jù)庫(kù)的數(shù)據(jù)分布有關(guān)系。查詢時(shí)MySQL會(huì)評(píng)估,走索引快,還是全表掃描快,如果全表掃描更快,則放棄索引走全表掃描。 因此,is null 、is not null是否走索引,得具體情況具體分析,并不是固定的。
6.4 SQL提示
目前tb_user表的索引情況如下:
(1)執(zhí)行SQL :
explain select * from tb_user where profession = '軟件工程';
查詢走了聯(lián)合索引。
(2)執(zhí)行SQL,創(chuàng)建profession的單列索引:
create index idx_user_pro on tb_user(profession);
創(chuàng)建單列索引后,再次執(zhí)行A中的SQL語(yǔ)句,查看執(zhí)行計(jì)劃,看看到底走哪個(gè)索引。
測(cè)試結(jié)果,我們可以看到,possible_keys中 idx_user_pro_age_sta,idx_user_pro 這兩個(gè)索引都可能用到,最終MySQL選擇了idx_user_pro_age_sta索引。這是MySQL自動(dòng)選擇的結(jié)果。
那么,我們能不能在查詢的時(shí)候,自己來(lái)指定使用哪個(gè)索引呢? 答案是肯定的,此時(shí)就可以借助于MySQL的SQL提示來(lái)完成。 接下來(lái),介紹一下SQL提示。
SQL提示,是優(yōu)化數(shù)據(jù)庫(kù)的一個(gè)重要手段,簡(jiǎn)單來(lái)說(shuō),就是在SQL語(yǔ)句中加入一些人為的提示來(lái)達(dá)到優(yōu)化操作的目的。
use index : 建議MySQL使用哪一個(gè)索引完成此次查詢(僅僅是建議,mysql內(nèi)部還會(huì)再次進(jìn)行評(píng)估)。
explain select * from tb_user use index(idx_user_pro) where profession = '軟件工 程'
;
示例演示:
ignore index : 忽略指定的索引。
explain select * from tb_user ignore index(idx_user_pro) where profession = '軟件工 程'
;
示例演示:
force index : 強(qiáng)制使用索引。
explain select * from tb_user force index(idx_user_pro) where profession = '軟件工 程';
示例演示:
6.5 覆蓋索引
盡量使用覆蓋索引,減少select *。 那么什么是覆蓋索引呢? 覆蓋索引是指 查詢使用了索引,并且需要返回的列,在該索引中已經(jīng)全部能夠找到 。說(shuō)白了就是避免回表查詢
接下來(lái),我們來(lái)看一組SQL的執(zhí)行計(jì)劃,看看執(zhí)行計(jì)劃的差別,然后再來(lái)具體做一個(gè)解析。
explain select id, profession from tb_user where profession = ‘軟件工程' and age = 31 and status = ‘0' ; explain select id,profession,age, status from tb_user where profession = ‘軟件工程' and age = 31 and status = ‘0' ; explain select id,profession,age, status, name from tb_user where profession = ‘軟 件工程' and age = 31 and status = ‘0' ; explain select * from tb_user where profession = ‘軟件工程' and age = 31 and status = ‘0';
mysql5.5執(zhí)行結(jié)果:
mysql8.0執(zhí)行結(jié)果:
從上述的執(zhí)行計(jì)劃我們可以看到,這四條SQL語(yǔ)句的執(zhí)行計(jì)劃前面所有的指標(biāo)都是一樣的,看不出來(lái)差異。但是此時(shí),我們主要關(guān)注的是后面的Extra。
在5.5版本當(dāng)中,前面兩條SQL的結(jié)果為 Using where; Using Index ;
而后面兩條SQL的結(jié)果為: Using where
。
在8.0版本當(dāng)中,前面兩條SQL的結(jié)果為 Using Index ;
而后面兩條SQL的結(jié)果為: NULL
。
因?yàn)?,在tb_user表中有一個(gè)聯(lián)合索引 idx_user_pro_age_sta,該索引關(guān)聯(lián)了三個(gè)字段profession、age、status,而這個(gè)索引也是一個(gè)二級(jí)索引,所以葉子節(jié)點(diǎn)下面掛的是這一行的主鍵id。 所以當(dāng)我們查詢返回的數(shù)據(jù)在 id、profession、age、status 之中,則直接走二級(jí)索引直接返回?cái)?shù)據(jù)了。 如果超出這個(gè)范圍,就需要拿到主鍵id,再去掃描聚集索引,再獲取額外的數(shù)據(jù)了,這個(gè)過(guò)程就是回表。 而我們?nèi)绻恢笔褂胹elect * 查詢返回所有字段值,很容易就會(huì)造成回表查詢(除非是根據(jù)主鍵查詢,此時(shí)只會(huì)掃描聚集索引)。
表結(jié)構(gòu)及索引示意圖:
id是主鍵,是一個(gè)聚集索引。 name字段建立了普通索引,是一個(gè)二級(jí)索引(輔助索引)。
執(zhí)行SQL : select * from tb_user where id = 2;
根據(jù)id查詢,直接走聚集索引查詢,一次索引掃描,直接返回?cái)?shù)據(jù),性能高。
執(zhí)行SQL:selet id,name from tb_user where name = 'Arm';
執(zhí)行SQL:selet id,name,gender from tb_user where name = 'Arm';
由于在name的二級(jí)索引中,不包含gender,所以,需要兩次索引掃描,也就是需要回表查詢,性能相對(duì)較差一點(diǎn)。
思考題: 一張表, 有四個(gè)字段(id, username, password, status), 由于數(shù)據(jù)量大, 需要對(duì)以下SQL語(yǔ)句進(jìn)行優(yōu)化, 該如何進(jìn)行才是最優(yōu)方案:
select id,username,password from tb_user where username ='zhangsan';
答案: 針對(duì)于 username, password建立聯(lián)合索引, sql為:create index idx_user_name_pass on tb_user(username,password);
這樣可以避免上述的SQL語(yǔ)句,在查詢的過(guò)程中,出現(xiàn)回表查詢。
6.6 前綴索引
當(dāng)字段類型為字符串(varchar,text,longtext等)時(shí),有時(shí)候需要索引很長(zhǎng)的字符串,這會(huì)讓索引變得很大,查詢時(shí),浪費(fèi)大量的磁盤IO, 影響查詢效率。此時(shí)可以只將字符串的一部分前綴,建立索引,這樣可以大大節(jié)約索引空間,從而提高索引效率。
語(yǔ)法:create index idx_xxxx on table_name(column(n)) ;
示例: 為tb_user表的email字段,建立長(zhǎng)度為5的前綴索引。
create index idx_email_5 on tb_user(email(5));
可以根據(jù)索引的選擇性來(lái)決定,而選擇性是指不重復(fù)的索引值(基數(shù))和數(shù)據(jù)表的記錄總數(shù)的比值,索引選擇性越高則查詢效率越高, 唯一索引的選擇性是1,這是最好的索引選擇性,性能也是最好的。
select count(distinct email) / count(*) from tb_user ; select count(distinct substring(email,1,5)) / count(*) from tb_user ;
前綴索引的查詢流程
6.7 單列索引與聯(lián)合索引
- 單列索引:即一個(gè)索引只包含單個(gè)列。
- 聯(lián)合索引:即一個(gè)索引包含了多個(gè)列。
我們先來(lái)看看 tb_user 表中目前的索引情況:在查詢出來(lái)的索引中,既有單列索引,又有聯(lián)合索引。
接下來(lái),我們來(lái)執(zhí)行一條SQL語(yǔ)句,看看其執(zhí)行計(jì)劃:
通過(guò)上述執(zhí)行計(jì)劃我們可以看出來(lái),在and連接的兩個(gè)字段 phone、name上都是有單列索引的,但是最終mysql只會(huì)選擇一個(gè)索引,也就是說(shuō),只能走一個(gè)字段的索引,此時(shí)是會(huì)回表查詢的。
緊接著,我們?cè)賮?lái)創(chuàng)建一個(gè)phone和name字段的聯(lián)合索引來(lái)查詢一下執(zhí)行計(jì)劃。
此時(shí),查詢時(shí),就走了聯(lián)合索引,而在聯(lián)合索引中包含 phone、name的信息,在葉子節(jié)點(diǎn)下掛的是對(duì)應(yīng)的主鍵id,所以查詢是無(wú)需回表查詢的。
在業(yè)務(wù)場(chǎng)景中,如果存在多個(gè)查詢條件,考慮針對(duì)于查詢字段建立索引時(shí),建議建立聯(lián)合索引,而非單列索引。
如果查詢使用的是聯(lián)合索引,具體的結(jié)構(gòu)示意圖如下:
7. 索引設(shè)計(jì)原則
- 針對(duì)于數(shù)據(jù)量較大,且查詢比較頻繁的表建立索引。
- 針對(duì)于常作為查詢條件(where)、排序(order by)、分組(group by)操作的字段建立索引。
- 盡量選擇區(qū)分度高的列作為索引,盡量建立唯一索引,區(qū)分度越高,使用索引的效率越高。
- 如果是字符串類型的字段,字段的長(zhǎng)度較長(zhǎng),可以針對(duì)于字段的特點(diǎn),建立前綴索引。
- 盡量使用聯(lián)合索引,減少單列索引,查詢時(shí),聯(lián)合索引很多時(shí)候可以覆蓋索引,節(jié)省存儲(chǔ)空間,避免回表,提高查詢效率。
- 要控制索引的數(shù)量,索引并不是多多益善,索引越多,維護(hù)索引結(jié)構(gòu)的代價(jià)也就越大,會(huì)影響增刪改的效率。
create unique index idx_user_phone_name on tb_user(phone,name);
- 如果索引列不能存儲(chǔ)NULL值,請(qǐng)?jiān)趧?chuàng)建表時(shí)使用NOT NULL約束它。當(dāng)優(yōu)化器知道每列是否包含NULL值時(shí),它可以更好地確定哪個(gè)索引最有效地用于查詢。
本篇要求掌握:
- 什么是索引?
- 索引是個(gè)什么樣的數(shù)據(jù)結(jié)構(gòu)呢?
- 為什么使用索引?
- Hash 索引和 B+ 樹(shù)索引有什么區(qū)別或者說(shuō)優(yōu)劣呢?
- 什么是聚簇索引(一個(gè))
- 說(shuō)一說(shuō)索引的底層實(shí)現(xiàn)?(一般就是指的B+Tree)
- 索引有哪些優(yōu)缺點(diǎn)?
- 聚簇索引和非聚簇索引的區(qū)別(非聚簇索引就是二級(jí)索引)
- MySQL中有幾種索引類型,可以簡(jiǎn)單說(shuō)說(shuō)嗎?(主鍵,唯一,常規(guī)(常規(guī)又分為了多列和單列,多列的一般稱之為組合索引),全文)
- 覆蓋索引是什么? (覆蓋索引指的就是查詢的列盡量是索引所覆蓋的列,這樣可以避免回表查)
- 非聚簇索引一定會(huì)回表查詢嗎?(假如恰好select的列是條件當(dāng)中用到的索引列,是不用回表的)
- 聯(lián)合索引是什么?為什么需要注意聯(lián)合索引中的順序?(最左前綴法則)
- 創(chuàng)建的索引有沒(méi)有被使用到?或者說(shuō)怎么才可以知道這條語(yǔ)句運(yùn)行很慢的原因?(使用explain)
- 索引什么情況下會(huì)失效?
- 為什么Mysql用B+樹(shù)做索引而不用B-樹(shù)或紅黑樹(shù)、二叉樹(shù)?
- 索引在什么情況下遵循最左前綴的規(guī)則?(組合索引)
總結(jié)
到此這篇關(guān)于Mysql索引(index)的文章就介紹到這了,更多相關(guān)Mysql索引詳解內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
連接遠(yuǎn)程mysql數(shù)據(jù)庫(kù)失敗常見(jiàn)原因及解決方案
這篇文章主要介紹了連接遠(yuǎn)程mysql數(shù)據(jù)庫(kù)失敗常見(jiàn)原因及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07MySQL 百萬(wàn)級(jí)數(shù)據(jù)的4種查詢優(yōu)化方式
本文講解了MySQL 百萬(wàn)級(jí)數(shù)據(jù)的4種查詢優(yōu)化方式,大家可以根據(jù)自身需求,選擇適合自己的優(yōu)化方式2021-06-06升級(jí)到MySQL5.7后開(kāi)發(fā)不得不注意的一些坑
這篇文章主要給大家介紹了關(guān)于升級(jí)到MySQL5.7后開(kāi)發(fā)不得不注意的一些坑,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-07-07linux環(huán)境下配置mysql5.6支持IPV6連接的方法
本文主要介紹在linux系統(tǒng)下,如何配置mysql支持IPV6的連接,本文圖文并茂給大家介紹的非常詳細(xì),具有參考借鑒價(jià)值,需要的朋友參考下吧2018-01-01MySQL因大事務(wù)導(dǎo)致的Insert慢實(shí)例分析
這篇文章主要給大家介紹了關(guān)于MySQL因大事務(wù)導(dǎo)致Insert慢的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-10-10