索引是存儲(chǔ)引擎用于快速查找記錄的一種數(shù)據(jù)結(jié)構(gòu),我們可以通過合理的使用數(shù)據(jù)庫索引以提高數(shù)據(jù)庫的訪問效率。接下來主要介紹在MySQL 數(shù)據(jù)庫中索引類型,以及如何創(chuàng)建出更加合理且高效的索引技巧。
MySQL數(shù)據(jù)庫的內(nèi)部索引是由不同的存儲(chǔ)引擎實(shí)現(xiàn)的,本文主要介紹一下 InnoDB存儲(chǔ)引擎中的索引,InnoDB引擎中的索引是使用 B+樹 的結(jié)構(gòu)來存儲(chǔ)的。
InnoDB引擎中的B+樹結(jié)構(gòu)
接下來我們看一下B+ 樹結(jié)構(gòu),如下圖:

首先,說一下B+ 樹的特點(diǎn):
- 葉子節(jié)點(diǎn)(即最下面的一層)存儲(chǔ)關(guān)鍵字(索引字段的值)信息及對(duì)應(yīng)的整行數(shù)據(jù)記錄,即葉子節(jié)點(diǎn)存儲(chǔ)了所有記錄的關(guān)鍵字信息。
- 非葉子節(jié)點(diǎn)只存儲(chǔ)關(guān)鍵字的信息及子節(jié)點(diǎn)的指針。
- 每個(gè)葉子節(jié)點(diǎn)相當(dāng)于MySQL中的一個(gè)數(shù)據(jù)頁,同層級(jí)的葉子節(jié)點(diǎn)以雙向鏈表的形式相連。
- 每頁中存儲(chǔ)了多條記錄,記錄之間使用單鏈表連接組成了一條有序的鏈表,順序是按照索引字段排序的。
- B+ 樹中查詢數(shù)據(jù)時(shí),每次查詢都是從根節(jié)點(diǎn)開始,一直需要搜索到葉子節(jié)點(diǎn)。
MySQL中頁是InnoDB引擎中存儲(chǔ)數(shù)據(jù)的基本單位(塊是文件系統(tǒng)操作的最小單位,扇區(qū)是磁盤操作的最小單位),數(shù)據(jù)是按數(shù)據(jù)頁為單位來讀寫的,和磁盤交互的時(shí)候都是以頁來進(jìn)行的,每個(gè)頁的大小默認(rèn)是16kb。也就是說,當(dāng)需要讀取一條記錄的時(shí)候,并不是將這個(gè)記錄本身從磁盤讀取出來,而是以頁為單位,將整個(gè)也加載到內(nèi)存中,一個(gè)頁中可能有很多記錄,然后在內(nèi)存中對(duì)頁通過二分法進(jìn)行查詢。
整體上來說MySQL中的索引用到了B+樹,鏈表,二分法查找,做到了快速定位目標(biāo)數(shù)據(jù),快速范圍查找。
InnoDB引擎中的索引類型
InnoDB引擎中有2種索引類型:主鍵索引(聚集索引)、輔助索引(非聚集索引)。
- 主鍵索引:每個(gè)表只有一個(gè)主鍵索引,葉子節(jié)點(diǎn)同時(shí)保存了主鍵的值以及對(duì)應(yīng)記錄的數(shù)據(jù)。
- 輔助索引:葉子節(jié)點(diǎn)保存了索引字段的值以及主鍵的值,與聚簇索引的區(qū)別在于輔助索引的葉子節(jié)點(diǎn)中存放的是主鍵索引的鍵值。
如下Person 表,id 作為主鍵索引,name 作為輔助索引。

結(jié)合如上圖中 Person表,InnoDB引擎數(shù)據(jù)查詢過程如下:
如果需要查詢 id=1 的數(shù)據(jù),只需要通過主鍵索引(聚集索引)中查詢就可以了。
如果需要查詢 name='Jacy' 的數(shù)據(jù),需要使用非聚集索引與聚集索引,需要2步:
- 首先,通過輔助索引中查詢 name='Jacy' 的數(shù)據(jù),獲取id的值為 12。
- 然后,根據(jù)id再到主鍵索引中查詢 id=12 的數(shù)據(jù)記錄。
如上,這個(gè)查詢過程在MySQL中叫做 回表,下面我們會(huì)具體介紹回表。
聚集索引(主鍵索引)
聚集索引中鍵值的邏輯順序決定了表中相應(yīng)行的物理順序(索引中的數(shù)據(jù)物理存放地址和索引的順序是一致的),可以這么理解:只要是索引是連續(xù)的,那么數(shù)據(jù)在存儲(chǔ)介質(zhì)上的存儲(chǔ)位置也是連續(xù)的。 比方說:想要到字典上查找一個(gè)字,我們可以根據(jù)字典前面的拼音找到該字,注意拼音的排列時(shí)有順序的。
聚集索引就像我們根據(jù)拼音的順序查字典類似,可以大大的提高效率。在經(jīng)常搜索一定范圍的值時(shí),通過索引找到第一條數(shù)據(jù),根據(jù)物理地址連續(xù)存儲(chǔ)的特點(diǎn),然后查詢相鄰的數(shù)據(jù),直到到達(dá)條件截止項(xiàng)。
每個(gè)表一定會(huì)有一個(gè)聚集索引,整個(gè)表的數(shù)據(jù)存儲(chǔ)以B+ 樹的方式存在文件中,葉子節(jié)點(diǎn)中的key為主鍵值,data為完整的整行記錄的信息,非葉子節(jié)點(diǎn)存儲(chǔ)主鍵的值。
通過聚集索引查詢數(shù)據(jù)只需要按照B+ 樹的搜索過程,即可以查詢到對(duì)應(yīng)的記錄。聚集索引按照如下規(guī)則創(chuàng)建:
- 當(dāng)定義了主鍵后,InnoDB會(huì)利用主鍵來生成其聚集索引。
- 如果沒有主鍵,InnoDB會(huì)選擇第一個(gè)非空的唯一索引來創(chuàng)建聚集索引。
- 如果合適的非空的唯一索引,InnoDB會(huì)隱式的創(chuàng)建一個(gè)6個(gè)字節(jié)的自增的列來作為聚集索引,該列的值會(huì)隨著數(shù)據(jù)的插入自增。
非聚集索引
索引的邏輯順序與磁盤上的物理存儲(chǔ)順序不同。非聚集索引的鍵值在邏輯上也是連續(xù)的,但是表中的數(shù)據(jù)在存儲(chǔ)介質(zhì)上的物理順序是不一致的,即記錄的邏輯順序和實(shí)際存儲(chǔ)的物理順序沒有任何聯(lián)系。索引的記錄節(jié)點(diǎn)有一個(gè)數(shù)據(jù)指針指向真正的數(shù)據(jù)存儲(chǔ)位置。
非聚集索引就像根據(jù)偏旁部首查字典一樣,字典前面的目錄在邏輯上也是連續(xù)的,但是查兩個(gè)偏旁在目錄上挨著的字時(shí),字典中的字卻很不可能是挨著的。
每個(gè)表可以有多個(gè)非聚集索引,B+ 樹結(jié)構(gòu),葉子節(jié)點(diǎn)的key為索引字段的值,data為主鍵的值。非葉子節(jié)點(diǎn)只存儲(chǔ)索引字段的值。
通過非聚集索引查詢記錄的時(shí)候,可能需要2次操作,先在非聚集索引中查詢出主鍵,然后再到聚集索引中查詢出主鍵對(duì)應(yīng)的行記錄,也就是進(jìn)行兩次 B+樹查詢。
InnoDB引擎中B+樹的數(shù)據(jù)查詢過程
我們?cè)诓樵冞^程中,當(dāng)使用多個(gè)索引時(shí),InnoDB引擎使用的哪個(gè)索引,為什么有時(shí)候雖然使用了索引,但看執(zhí)行計(jì)劃卻顯示沒有使用索引,這弄清楚這些之前。我們先看一下B+ 樹查詢數(shù)據(jù)的過程。
主鍵或唯一索引查詢

如上圖,所有的數(shù)據(jù)都是唯一的,我們查詢 26 的記錄,過程如下:
- 將Data page1頁加載到內(nèi)存,在內(nèi)存中采用二分法查找。
- 確定26位于[20,40) 中間,然后再去加載20關(guān)聯(lián)的 Data page3頁。
- 將Data page3頁加載到內(nèi)存中,通過采用二分法找到26 的記錄后退出。
非唯一索引查詢

如上圖,數(shù)據(jù)為并不是唯一的,我們查詢26 的所有記錄,過程如下:
- 將Data page1頁加載到內(nèi)存,在內(nèi)存中通過二分法查找。
- 確定26位于 [20,40)中間,然后再去加載20關(guān)聯(lián)的 Data page3 頁。
- 將Data page3 頁加載到內(nèi)存中,通過二分法找到最后一個(gè)小于 26的記錄,即 23,然后通過鏈表從23 開始向后訪問,找到所有的26記錄,直到遇到第一個(gè)大于26的值時(shí)停止。
范圍查詢

如上圖,查詢 [25,45] 所有記錄,由于數(shù)據(jù)頁之間是雙向鏈表升序結(jié)構(gòu),頁內(nèi)部的數(shù)據(jù)是單項(xiàng)升序鏈表結(jié)構(gòu),所以只用找到范圍的起始值所在的位置,然后通過依靠鏈表訪問兩個(gè)位置之間所有的數(shù)據(jù)即可,過程如下:
- 將Data page1 頁加載到內(nèi)存,在內(nèi)存中通過二分法查找。
- 確定25位于 20關(guān)聯(lián)的Data page3 頁中,45位于40關(guān)聯(lián)的Data page4 頁中。
- 將Data page3 頁加載到內(nèi)存中,通過二分法找到第一個(gè)25的記錄,然后通過鏈表結(jié)構(gòu)繼續(xù)向后訪問Data page3頁中的 26,當(dāng)Data page3頁訪問完畢之后,通過Data page3頁的nextpage指針訪問下一頁Data page4 頁,同樣加載到內(nèi)存中,通過二分法查找所有小于45的記錄。
模糊匹配查詢

如上圖,我們查詢以 b 字母開頭的所有記錄,過程如下:
- 將Data page1 頁數(shù)據(jù)加載到內(nèi)存中。
- 通過二分法查找最后一個(gè)小于等于b 的值,即b,以及第一個(gè)大于b的,即z。b指向葉節(jié)點(diǎn)Data page3 頁,z指向葉節(jié)點(diǎn)Data page5 頁,確定以f開頭的記錄可能存在于[Data page3,Data page5)這個(gè)范圍的頁內(nèi),即Data page3、Data page4 這兩頁中。
- 依次加載Data page3 到內(nèi)存中,通過二分法找到第一條b 開頭的記錄,然后以鏈表方式繼續(xù)向后訪問Data page4 頁中的記錄,即可以找到所有以 b 開頭的記錄。
當(dāng)我們?cè)赟QL中使用LIKE %b%全模糊查詢時(shí),執(zhí)行過程是什么樣的呢?
如上圖,b在每個(gè)頁中都存在,我們通過Data page1 頁中的記錄是無法判斷包含b的記錄在哪些頁的,只能加載所有葉子節(jié)點(diǎn)(頁),然后遍歷所有記錄進(jìn)行過濾,才可以找到包含b 的記錄。所以如果使用了LIKE %b%全模糊查詢,索引對(duì)查詢是無效的。
復(fù)合索引的最左匹配原則

當(dāng)B+ 樹的數(shù)據(jù)項(xiàng)是復(fù)合的數(shù)據(jù)結(jié)構(gòu),比如(name,age,sex) 的時(shí)候,B+ 樹是按照從左到右的順序來建立搜索樹的,比如當(dāng)使用 (Tony, 20, 男) 查詢時(shí),B+ 樹會(huì)優(yōu)先比較 name 來確定下一步的查詢方向,如果name 相同再依次比較 age 和sex ,最后得到查詢的數(shù)據(jù)。
但使用(20, 男)這樣的沒有name的數(shù)據(jù)來查詢的時(shí)候,B+ 樹則不知道下一步該查哪個(gè)節(jié)點(diǎn),因?yàn)榻⑺阉鳂涞臅r(shí)候name 就是第一個(gè)索引字段,必須要先根據(jù)name 來搜索才能知道下一步去哪里查詢。
比如當(dāng)使用 (Tony, 男) 查詢時(shí),B+ 樹可以用name 來指定搜索方向,但下一個(gè)字段age 的缺失,所以只能把名字等于Tony 的數(shù)據(jù)都找到,然后再匹配性別是男 的數(shù)據(jù)了, 即索引的最左匹配特性,如上圖。

同時(shí),在上圖中,將 a, b, c 3個(gè)字段建立為復(fù)合索引(a,b,c),索引中數(shù)據(jù)的順序是以a asc, b asc, c asc這種排序方式存儲(chǔ)在節(jié)點(diǎn)中的,索引先以a字段升序,如果a相同的時(shí)候,再以b字段升序,b相同的時(shí)候,再以c字段升序。
我們分別看下,當(dāng)使用以下字段進(jìn)行查詢時(shí),查詢過程是什么樣子的。
- 查詢 a=1 的記錄
由于頁中的記錄是以a asc,b asc,c asc這種排序方式存儲(chǔ)的,所以a字段是有序的,可以通過二分法快速查詢到,過程如下:
1.將Data page 1加載到內(nèi)存中,通過二分法查找,可以確定a=1的記錄位于{1,1,1}和{1,6,1}內(nèi),關(guān)聯(lián)Data page2與Data page3 頁。
2.加載Data page 2 頁到內(nèi)存中,通過二分法快速找到第一條a=1的記錄,然后通過鏈表向下一條及下一頁Data page4 頁進(jìn)行查詢,直到找到第一個(gè)不滿足a=1的記錄為止。
- 查詢 a=1 and b=6 的記錄
首先可以確定a=1 and b=6的記錄位于{1,6,1}內(nèi),關(guān)聯(lián)Data page3 頁并加載到內(nèi)存中,后續(xù)查找過程和a=1 查找步驟類似。
- 查詢 c=1 的記錄
這種情況通過Data page 1頁中的記錄,無法判斷c=1的記錄在哪些頁中的,只能加載索引樹所有葉子節(jié)點(diǎn),對(duì)所有記錄進(jìn)行遍歷,然后進(jìn)行過濾,此時(shí)索引是無效的。
- 查詢 b=1 and c=1 的記錄
同上,這種也無法利用索引,只能進(jìn)行全表掃描,此時(shí)索引無效。
- 查詢 a=1 and c=1 的記錄
這種只能利用到索引中的a字段,通過a 確定索引范圍,然后加載a關(guān)聯(lián)的所有記錄,再對(duì)c的值進(jìn)行判斷過濾。
- 查詢 a=1 and b>=0 and c=1 的記錄
這種情況只能先確定a=1 and b>=0所在頁范圍,然后對(duì)這個(gè)范圍的所有頁進(jìn)行遍歷,c字段在這個(gè)查詢的過程中,是無法確定c的數(shù)據(jù)在哪些頁的,此時(shí)c的索引失效,只有a、b能夠有效的確定索引頁的范圍。
總結(jié),對(duì)于復(fù)合索引失效的可能原因有以下幾點(diǎn):
復(fù)合索引的生效原則是從前往后依次使用生效,如果中間某個(gè)索引沒有使用,那么僅斷點(diǎn)前面的索引部分起作用,斷點(diǎn)后面的索引不起作用,造成斷點(diǎn)的原因一般有:
- 前邊的任意一個(gè)索引沒有參與查詢,后面的不生效。
- 前邊的任意一個(gè)索引失效,當(dāng)前索引及后面全部不生效。
- 前邊的任意一個(gè)索引字段參與的是范圍查詢(>、<、between),后面的不生效。
索引區(qū)分度

如上圖,上面是兩個(gè)有序的數(shù)組,都是10條記錄,如果我們需要查詢值為6 的所有記錄,查詢這兩個(gè)數(shù)組哪個(gè)更快一些?
我們使用二分法查找包含6 的所有記錄過程如下:先使用二分法找到最后一個(gè)小于6的記錄,然后從這條記錄向后獲取下一個(gè)記錄,依次與6 比較,直到遇到第一個(gè)大于6 的數(shù)字結(jié)束,或者到達(dá)數(shù)組末尾結(jié)束,通過這種方法找到6 的記錄,第一個(gè)數(shù)組的查詢更快的一些。因?yàn)榈诙€(gè)數(shù)組中含有6的比例更多的,需要訪問以及匹配的次數(shù)更多一些。
這里涉及數(shù)據(jù)的區(qū)分度問題:索引區(qū)分度 = count(distint 字段) / count(字段)。
當(dāng)索引區(qū)分度高的時(shí)候,查詢數(shù)據(jù)要更快一些。當(dāng)索引區(qū)分度太低,說明重復(fù)的數(shù)據(jù)比較多,查詢的時(shí)候基本上接近于全索引數(shù)據(jù)的掃描了,此時(shí)查詢速度是比較慢的。
如上圖中,第一個(gè)數(shù)組的索引區(qū)分度為 0.9,第二個(gè)數(shù)組的索引區(qū)分度為0.2,所以第一個(gè)有序數(shù)組的查詢效率更快一些。
結(jié)合實(shí)例理解索引的正確使用方式
為了更好地理解上述內(nèi)容,我們以如下測試數(shù)據(jù) nickname_information 表為例,其包含編號(hào)、姓名、性別、昵稱 4個(gè)字段,其中除性別字段存在重復(fù)值,其余各字段均不重復(fù),共300萬條測試數(shù)據(jù)。

包含多個(gè)索引時(shí),查詢?nèi)绾芜x擇?
我們?cè)?strong>name、sex 兩個(gè)字段上分別創(chuàng)建索引 idx_name,idx_sex,如下:

查詢姓名為testops1000001 并且性別為女的所有信息:

我么可以看到執(zhí)行之間不到1ms,name與sex都是索引字段,那么實(shí)際執(zhí)行時(shí)使用的是哪個(gè)索引?
我們或許會(huì)說是根據(jù)WHERE 子句后的索引字段順序,name 位于WHERE 第一個(gè),所以走的是name字段所在的索引?執(zhí)行過程如下:
- 首先,根據(jù)name 所在的索引找到testops1000001 對(duì)應(yīng)的主鍵索引。
- 然后,根據(jù)主鍵索引查詢所有數(shù)據(jù)記錄。
- 最后,遍歷所有數(shù)據(jù)記錄過濾出sex=1 的值。
我們看一下 name='testops1000001' 查詢速度,如下:

那么索引的選擇真的與WHERE子句的索引字段順序有關(guān)么?我們把name 和sex 的順序?qū)φ{(diào)一下,如下:

查詢速度仍然很快,這次是不是先通過sex 索引查詢出數(shù)據(jù),然后再過濾name 呢?我們?cè)賮砜匆幌聅ex=1查詢速度:

看上面,查詢耗時(shí)220毫秒,150萬數(shù)據(jù),因此,肯定不是使用的sex 的索引。我們使用 explain來看一下SQL執(zhí)行計(jì)劃。

我們通過執(zhí)行計(jì)劃,可以看到該SQL中可能使用的索引(possible_keys)包含idx_name,idx_sex ,但實(shí)際使用的索引(key)是idx_name。
因此,當(dāng)WHERE子句中包含多個(gè)索引字段并且關(guān)系是 AND 時(shí),會(huì)使用索引區(qū)分度高的索引字段,如上例子中,顯然name 字段不存在重復(fù)度,并且age 字段的重復(fù)度很高,因此會(huì)使用name 查詢會(huì)更快一些。
模糊查詢

如上兩個(gè)SQL查詢語句,第一個(gè)使用后模糊查詢('testops1000%'),第二個(gè)使用全模糊查詢(%testops1000%')。
第一個(gè)SQL語句可以利用到name 字段上面的索引,第二個(gè)SQL語句因無法確定需要查找的值所在的范圍的,只能全表掃描,無法利用索引,所以速度比較慢。
WHERE 子句中使用 LIKE 進(jìn)行模糊查詢時(shí),在關(guān)鍵詞前加通配符或者前后都加通配號(hào)都無法使用索引,從而引發(fā)全表掃描。解決LIKE '%abc%' 時(shí)索引不被使用的方法就是添加覆蓋索引(只訪問索引的查詢,索引和查詢列一致,只需掃描索引而無須回表)。
回表
當(dāng)需要查詢的字段在索引樹中不存在時(shí)(非索引字段),需要再次到聚集索引中去獲取,這個(gè)過程叫做回表,如查詢:

如上SQL查詢語句中查詢是所有字段,由于name 列所在的索引中只有name、id(主鍵索引) 兩個(gè)列的值,不包含sex、nickname,所以上面過程如下:
- 首先,通過name 索引查詢 testops1000001 對(duì)應(yīng)的記錄,取出id為1000001。
- 然后,在主鍵索引中查詢 id=1000001 的記錄,獲取整行中所有字段的值。
索引覆蓋
當(dāng)查詢中采用的索引樹中包含了要查詢的所有字段,不需要再去聚集索引查詢數(shù)據(jù),這種叫索引覆蓋。或者說查詢語句的執(zhí)行只需要從輔助索引中就可以得到查詢記錄,而不需要查詢聚集索引中的記錄。

如上SQL查詢語句中,name 對(duì)應(yīng)idx_name 索引,id為主鍵索引,所以idx_name 索引樹葉子節(jié)點(diǎn)中包含了id、name的值,這個(gè)查詢只用走 idx_name這一個(gè)索引就可以了。若改為查詢?nèi)孔侄危€需要一次回表獲取sex、nickname的值,則不是索引覆蓋。
所以設(shè)計(jì)SQL的時(shí)候,盡量避免使用'*',可能會(huì)多一次回表操作,同時(shí)需要關(guān)注是否可以使用索引覆蓋來實(shí)現(xiàn),效率更高一些。
索引下推
Index Condition Pushdown(ICP) 是MySQL 5.6中新特性,是一種在存儲(chǔ)引擎層使用索引過濾數(shù)據(jù)的一種優(yōu)化方式,ICP可以減少存儲(chǔ)引擎訪問基表的次數(shù)以及MySQL服務(wù)器訪問存儲(chǔ)引擎的次數(shù)。
舉個(gè)例子,我們需要查詢name以 testops1000 開頭的并且性別為男 的記錄數(shù),SQL語句如下:

如上SQL查詢語句,存在2種可能執(zhí)行的過程:
第一種執(zhí)行方式,如下:
- 首先,通過name 索引查詢出以testops1000 的第一條記錄,得到記錄的 id。
- 然后,利用id 去主鍵索引中查詢出這條記錄T1。
- 最后,判斷 T1.sex 是否為1,然后重復(fù)上面的操作,直到找到所有記錄為止。
上面的過程中需要走name索引以及需要回表操作。
第二種執(zhí)行方式,通過ICP的方式,我們可以這么做,創(chuàng)建一個(gè)(name, sex) 的組合索引,查詢過程如下:
- 首先,通過(name,sex) 索引查詢出以 testops1000 的第一條記錄,可以得到(name, sex, id),記做T1。
- 然后,判斷 T1.sex 是否為1,然后重復(fù)上面的操作,知道找到所有記錄為止。
這個(gè)過程中不需要回表操作了,通過索引的數(shù)據(jù)就可以完成整個(gè)條件的過濾,速度比上面的更快一些。若當(dāng)我們select全部字段時(shí),索引下推可以減少回表查詢的范圍次數(shù)。
需要注意的是:索引下推一般可用于所求查詢字段(SELECT列)不是或者不全是聯(lián)合索引的字段,查詢條件為多條件查詢且查詢條件子句(WHERE、ORDER BY)字段全是聯(lián)合索引。
假設(shè)表table1 有聯(lián)合索引(a,b),下面語句可以使用索引下推提高效率 SELECT * FROM table1 WHERE a > 2 AND b > 10;
類型錯(cuò)誤使索引失效

如上SQL查詢語句中,第2條查詢很快,第三條用name 和 10086 比較,name為索引字段且字段類型為字符串類型,當(dāng)字符串和數(shù)字比較的時(shí)候,會(huì)將字符串強(qiáng)制轉(zhuǎn)換為數(shù)字,然后進(jìn)行比較,所以第3個(gè)查詢變成了全表掃描。
那么如果字段類型是數(shù)字類型,查詢時(shí)使用字符串類型,會(huì)怎樣?如下:

id上面有主鍵索引,id 是int 類型的,可以看到,上面兩個(gè)查詢都非常快,都可以正常利用索引快速查詢,所以如果字段是數(shù)字類型,查詢的值無論是字符串還是數(shù)組都會(huì)走索引。
函數(shù)使索引失效

name字段上建立索引,如上SQL查詢語句中,第一個(gè)查詢SQL使用索引,第二個(gè)查詢SQL不使用索引。
第二個(gè)SQL對(duì)name 字段使用了concat 函數(shù)之后,name所在的索引樹無法快速定位需要查找的數(shù)據(jù)所在的頁,只能將所有頁的記錄加載到內(nèi)存中,然后對(duì)每條數(shù)據(jù)使用concat 函數(shù)進(jìn)行計(jì)算之后再進(jìn)行條件判斷,此時(shí)索引失效,進(jìn)行全表數(shù)據(jù)掃描。
應(yīng)盡量避免在 WHERE 子句中對(duì) “=” 左邊的字段進(jìn)行函數(shù)表達(dá)式運(yùn)算,可以將表達(dá)式運(yùn)算移至“=”右邊,否則將導(dǎo)致引擎放棄使用索引而進(jìn)行全表掃描。
運(yùn)算符使索引失效

id字段上建立主鍵索引,如上SQL查詢語句中,第一個(gè)查詢SQL使用索引,第二個(gè)查詢SQL不使用索引。
第二個(gè)SQL中對(duì)id 使用運(yùn)算符,id所在的索引樹是無法快速定位需要查找的數(shù)據(jù)所在的頁,只能將所有頁的記錄加載到內(nèi)存中,然后對(duì)每條數(shù)據(jù)的id 進(jìn)行計(jì)算之后再判斷是否等于 2,此時(shí)索引失效,進(jìn)行全表數(shù)據(jù)掃描。
應(yīng)盡量避免在 WHERE 子句中對(duì) “=” 左邊的字段進(jìn)行算術(shù)運(yùn)算,可以將表達(dá)式運(yùn)算移至“=”右邊,否則將導(dǎo)致引擎放棄使用索引而進(jìn)行全表掃描。
索引的一些建議
- 復(fù)合索引要遵守最佳左前綴原則,必須按照從左到右的順序匹配,MySQL會(huì)一直向右匹配直到遇到斷點(diǎn)(>、<、between、like)或者索引字段失效就停止匹配,比如a = 1 and b > 2 and c = 3 如果建立(a,b,c)順序的索引,c則用不到索引的。
- 查詢記錄的時(shí)候,避免使用*,盡量利用索引覆蓋,減少回表操作,提升效率。
- 索引下推一般可用于查詢條件為多條件查詢且查詢條件子句(WHERE、ORDER BY)字段全是復(fù)合索引,可以減少回表操作,提升效率。
- 避免對(duì)索引字段使用函數(shù)、運(yùn)算符操作,會(huì)使索引失效。
- 類型錯(cuò)誤,如字段num類型為varchar,WHERE條件用NUM = 1,會(huì)使索引失效。
- 模糊查詢LIKE 以通配符開頭如,%ab,會(huì)使索引無效,變?yōu)槿頀呙瑁?#39;值%'這種可以有效利用索引。
- 排序中盡量使用到索引字段,這樣可以減少排序,提升查詢效率。
- 在區(qū)分度高的字段上面建立索引可以有效的使用索引,區(qū)分度太低,無法有效的利用索引,可能需要掃描所有數(shù)據(jù)頁,此時(shí)和不使用索引幾乎無區(qū)別。