索引优化策略(上)
正確的創(chuàng)建和使用索引,是實現(xiàn)數(shù)據(jù)庫高性能查詢的基礎(chǔ),前面我們已經(jīng)為大家介紹了,常用的MYSQL的類型,以及其優(yōu)點和缺點,下面我們再來看看如何正確的使用這些索引,以發(fā)揮索引的優(yōu)勢,提高數(shù)據(jù)庫的性能,我們先來看看第一個索引的使用策略,索引列上不能使用表達式或函數(shù),但是實際工作中呢,還是經(jīng)常會看到,違法這個策略的查詢的出現(xiàn),如下面這個查詢,這個查詢的目的是為了查找出,哪些在三十天內(nèi),就過期的商品
針對這個查詢呢,就算是我們在過期日志out_date上面,建立了一個B樹索引,那么這個查詢呢,也無法用到這個索引,這是因為在這個查詢中呢,我們在out_date這個列上呢,使用了函數(shù),所以也就違反了我們上面所說的,使用索引的第一個策略,索引列上不能使用表達式或者函數(shù),那么要想讓這個查詢,可以使用到索引,我們應該如何做呢,你需要把這個查詢改成下面這樣了,經(jīng)過這樣的改寫之后,在查詢中,out_date列上呢,不會再使用函數(shù)了,因此這時就可以正確的使用到該列上的索引了,對于索引中的日期列,進行計算,是大家使用日期列進行查找時呢,很常見的一種做法,也是在使用日期列上的索引,最長犯的錯誤,希望大家在進行這類查詢時呢,要注意這個問題,以正確的方法,使用日期列上的索引
MYSQL中的B樹索引,對鍵值的大小是有限制的,這個限制根據(jù)所使用的存儲引擎的不同而不同,比如對于Innodb存儲引擎來說,索引建立的最大大小呢,不能超過767個字節(jié),而MYISAM中呢,這個限制則是1000個字節(jié),這個限制對于整形,浮點型,以及日期來說,已經(jīng)是足夠了,但是對于字符串類型來說呢,可能就會有些不足,一方面是對于大的字符串,特別是text字符串呢,可以在這個列上建立索引,另外一方面如果是字符串很長的列呢,也會對索引變得很慢,這樣就達不到索引優(yōu)化的目的,所以MYSQL支持,對字符串的前綴,建立索引,這樣可以大大的節(jié)約這種索引的空間,從而提高查詢的效率,我們給列的前綴建立索引呢,我們可以使用,下面的這個語法create index index_name on table(col_name(n));也就是在create index的語句中呢,指定列的一個寬度,大家一定要記住,這個列的寬度是有限制的,對于innodb來說呢,索引列的寬度呢,最大是767個字節(jié),如果我們使用的是utf8字符集的話,算成字符的話,我們差不多也就是255個字符,而對于MYISAM表呢,前面也說過了,索引列的寬度呢,最大值是1000,如果超過這個最大寬度的限制呢,是無法建立前綴索引的,雖然我們可以在大的字符列上,使用前綴索引呢,來增加索引的效率,但是前綴索引呢,會降低了索引的選擇性,所謂索引的選擇性呢,就是不重復的索引鍵值,和表的總記錄的比率,也就是說,索引鍵值的唯一性越高,其選擇性也就越高,由此可見,唯一索引,或者主鍵索引的選擇性呢,是最高的,索引選擇性越高呢,使用索引查找的效率也就越快,在后面介紹聯(lián)合索引時呢,大家就會發(fā)現(xiàn),索引鍵值的選擇性呢,決定了聯(lián)合索引中,鍵值的順序,回到我們的前綴索引中,前綴索引只是取了字符串的一部分,來作為索引的鍵值,所以其選擇性必然會降低,我們可以看到下面的幾列數(shù)值,大家可能就明白我的意思了
首先我們有這么三列字符串,abcd,abde,bcdef,如果我們這時選擇前兩個字節(jié),建立前綴索引,那么其不同的值呢,只有兩個,ab和bc,而如果我們選擇列的三個字母,采用前綴索引的話呢,那么不同值就變成了4個,所以說呢,前綴索引的長度,和索引的可選擇性是相關(guān)的,我們在選用索引前綴的長度時,一定要注意,既要做到索引盡可能的小,同時也要保證索引的選擇性不要太差
我們在使用索引時的一個常見誤區(qū),是在表中的每一列上呢,都要建立一個索引,這種情況通常會發(fā)生一些,對數(shù)據(jù)庫索引不太了解的開發(fā)人員的身上,他們知道索引的好處,但是不知道如何建立合適的索引,同時又聽到一些有經(jīng)驗的人說呢,要把所有查詢的列都建立上索引,于是就出現(xiàn)把表中的每一列上,建立索引的一個情況,因為表中的每一列呢,都有可能出現(xiàn)在where條件中,心想不管哪一列作為過濾條件,反正每一列上都有索引了,查詢效率應該會很高,只所以說他們不了解索引,就會認為他們只想到索引可以提高查詢效率,太多的索引會對數(shù)據(jù)庫的性能呢,也是會有影響的,前面我們說過,為什么索引不能太多,其實從另一方面來講,每一列上都要建立一個索引呢,或者同時出現(xiàn)了多個列,MYSQL中也不一定能夠使用上這些索引,在MYSQL5.0之前,一個查詢只能使用到一個列上的索引,所以對于where條件中太多的這種列呢,如果存在多列的過濾條件呢,實際上并不能使用到這種每一列上的索引的,因此也沒有什么實際的意義,而在MYSQL5.0之后呢,雖然引入了索引合并的概念,在一個查詢中呢,可以使用多個列上的獨立索引,來進行合并過濾,但是通常意味著呢,這需要更多的內(nèi)存,和磁盤IO,來緩存這些每一個索引,所以這也不是一種好的優(yōu)化方式,而對于這種單例上的索引來說呢,在查詢中呢,建立多個列的聯(lián)合索引來進行優(yōu)化呢,可能才是一種更好的優(yōu)化方法,那么要建立聯(lián)合索引的話,聯(lián)合索引這種順序的選擇呢,尤為重要了,前面我們在介紹B樹索引時說過,B樹索引是按照索引的順序,進行存放的,而且索引列的先后順序呢,也與我們查詢是否可以使用到這種索引,那么下面我們就來看一看,應該如何選擇這種聯(lián)合索引中,索引列的這種順序,首先第一個原則呢,經(jīng)常會被使用到的列優(yōu)先的這種原則,如果我們非常頻繁的會使用某一個列,進行過濾,那我們就要把這一列放到聯(lián)合索引的最左邊,那么前面我們說過,索引列的順序呢,是按照從左向右的順序來使用的,對于出現(xiàn)在最左邊的列呢,不但在多個列的聯(lián)合索引中,可以被用到,就算是在單個列的過濾中呢,也同樣會被用到,所以我們要把經(jīng)常被查詢的列呢,放到聯(lián)合索引的最左邊,當然這也并不是絕對的,如果我們使用到的過濾條件呢,像狀態(tài)列這樣的列,本身的選擇性能很差,那么就不適合放在聯(lián)合索引最左邊了,因為選擇性很差的列呢,如果就算是建立了索引,MYSQL的優(yōu)化器呢,也不見得會使用到這個索引,因為MYSQL優(yōu)化器呢,會認為在這種情況使用全表掃描,要比使用索引的成本更低,所以也就引出了我們選擇這個索引列的第二個原則,也就是選擇性列高的原則,選擇性高呢往往意味著,可以過濾出更多的數(shù)據(jù),特別是在where條件中,選擇性越高查詢效率也就越快,如果聯(lián)合索引最左邊的列呢,選擇性很高,那么在完成最左邊列過濾后,能夠滿足條件的數(shù)據(jù)呢,也就沒有多少了,這樣在進行后面的過濾呢,要快的多,索引順序選擇的第三個原則呢,哪些寬度比較小的列,優(yōu)先使用,當然了,這是不違反選擇性條件下,來進行的,寬度越小,就意味著一列所存儲的索引越多,也就代表了,使用索引過濾時候呢,IO會越小,同時也加快了索引的查詢效率
我們在進行索引優(yōu)化時,關(guān)注的重點,通常是where字句中的過濾條件,其實我們在前面介紹B樹索引時呢,也說過,B樹索引呢,除了可以幫我們加快過濾數(shù)據(jù),我們所需要的數(shù)據(jù)內(nèi)容外,還可以做很多其他的事情,比如說排序分組,其實除了這些之外,我們還可以通過索引直接獲取我們的數(shù)據(jù),B樹索引和哈希索引是不同的,B樹索引的葉子節(jié)點上呢,存儲了索引關(guān)鍵字的值,所以如果我們可以通過索引的關(guān)鍵字,直接獲取你要查詢的數(shù)據(jù),這樣就沒有必要讀取索引行的信息了,這種包含了所有需要查詢字段全部的索引,我們就稱之為覆蓋索引,這里說的全部索引,不但指where字句中,所出現(xiàn)列的值,同時也包括了select中的值,order by和group by中的值,說到了什么是覆蓋索引,那下面我們來看看使用覆蓋索引會給我們的優(yōu)化數(shù)據(jù)庫的性能,帶來什么樣的好處,通常對于一個表來說,比如一個有30列的表,那么每個索引呢,就算是覆蓋索引,也不過就是包括這30列的幾列數(shù)據(jù),所以索引中的條目,通常會遠小于數(shù)據(jù)行的大小,因此如果我們只需要讀取索引,能夠完成我們的查詢的話,那么MYSQL就會極大地減少數(shù)據(jù)的訪問量,因為索引比較小,索引中就可以緩沖更多的數(shù)據(jù),減少數(shù)據(jù)從磁盤和數(shù)據(jù)庫緩存之間,這樣就優(yōu)化了緩存的效率,減少了磁盤IO的操作,同時由于B樹索引是按照鍵值的順序來存儲的,會比隨機從磁盤讀取每一行讀取的數(shù)據(jù)要小的多,而且B樹索引在訪問時呢,磁盤隨機的IO呢改變成順序的IO,而通過我們前面的內(nèi)容呢,應該知道,磁盤設(shè)備在處理順序IO時呢,要比隨機IO快的多,所以這樣就更有利于查詢的速度,前面我們也介紹過了,對于innodb存儲引擎來說,二級索引在葉子節(jié)點中保存的是行的主鍵值,在通常情況下,如果是利用二級索引來查詢數(shù)據(jù)的話,那么在查找到相應的鍵之后,還要通過主鍵來進行二次的查詢,才能獲取我們所需要行的數(shù)據(jù),而在覆蓋索引中呢,二級索引的鍵值呢,可以獲取我們查詢所要的所有數(shù)據(jù)了,這樣就避免了對主鍵的二次查詢,也就減少了相關(guān)的IO操作,那么Innodb存儲引擎的表呢,通過覆蓋索引呢,來獲得必要的好處,同時對于MYISAM表來說呢,他獲得好處呢可能會更大,那么對于MYISAM存儲引擎來說,MYSQL只會緩存這種,索引的信息,數(shù)據(jù)則是依賴于操作系統(tǒng)來緩存,因此我們?nèi)绻L問數(shù)據(jù),而系統(tǒng)調(diào)用的性能呢,通常會比較差,所以很有可能會產(chǎn)生嚴重的性能問題,所以如果能夠通過索引,來獲取全部所需要的數(shù)據(jù)呢,就可以避免系統(tǒng)調(diào)用的產(chǎn)生,覆蓋索引對于MYISAM存儲引擎的查詢呢,更為的有效
和所有的優(yōu)化方法一樣,覆蓋索引也不是解決性能問題的靈丹妙藥,換句話說,并不是什么樣的性能問題呢,都可以通過覆蓋索引來解決,在下列的場景中呢,覆蓋索引就無法使用,首先來說,并不是所有的存儲引擎,都支持覆蓋索引,比如我們前面介紹過的,memory存儲引擎,他就不支持覆蓋索引,而且不是所有的索引類型呢,都能夠建立這種覆蓋索引,通過對覆蓋索引的介紹,我們知道,只有在索引的葉子節(jié)點中,包括了鍵值的索引,所以我們之前所介紹的哈希索引呢,就不能夠作為覆蓋索引來使用,其次,如果在查詢中包含了太多的列,這樣也不太適合建立覆蓋索引,覆蓋索引之所以能夠提高查詢的性能,主要是因為索引的大小呢,要比行的大小要小的多,如果索引本身很大,那么就沒有使用覆蓋索引的必要了,特別是對于select *這樣的查詢,對于查詢了所有列,不可能有索引可以覆蓋到所有的列,所以對于這種的查詢呢,也是無法使用B樹索引來優(yōu)化的,最后呢對于MYSQL,不能使用雙百分號的select查詢,如果查詢中存在雙百分號的like條件,可能就無法使用覆蓋索引了,這是由于MYSQL底層存儲引擎的API所限制的,因為這樣的查詢呢,MYSQL只能提取數(shù)據(jù)行的值,然后在內(nèi)存中,再進行過濾,所以同樣不能夠使用了,所以更別說覆蓋索引了,我們可以來看幾個覆蓋索引的列子,這樣大家就更能明白什么樣的索引是覆蓋索引
接下來我們進入到我們的演示系統(tǒng)中,來給大家講解一下,什么是覆蓋索引,我們說一下這個表的查詢計劃是什么樣子,首先我們查詢出language_id列,我們只要這一列desc film;
language_id列,我們只要這一列,我們查詢language_id等于1的這一列數(shù)據(jù)explain select language_id from film where language_id = 1\G
那么現(xiàn)在這個查詢計劃呢,不是一個最簡單的覆蓋查詢計劃,因為film表在language_id列上呢,有一個單例的索引,而且我們在select從句中呢,也只是返回了language_id,所以完全可以使用索引來獲取我們所需要的信息,這個就是在查詢計劃的explain顯示了using index的信息,我們會使用索引來獲取到數(shù)據(jù),但是如果我們把查詢改成下面這種形式時呢,大家來看一看,查詢計劃會變成什么樣子,我們這時會返回所有的列,用select *的方式explain select * from film where language_id = 1\G
大家這個時候可以看到,這兩個查詢計劃的,explain部分呢,一個是using index,而另一個是using where,這就說明了最大的查詢呢,不能從索引中獲取所有的數(shù)據(jù)的,也就是說,不能夠使用到覆蓋索引,而必須是把索引數(shù)據(jù)呢,先增加在內(nèi)存中,然后再進行where條件的過濾,這也就是我們剛才所說的,在使用覆蓋索引的時候呢,一個限制,就是對于select *的查詢呢,無法使用覆蓋索引來進行覆蓋的,那么我們再來看看下面的查詢,比如我們使用actor表,我們來使用這個表show create table actor\G
再進行查詢,在這里我們查詢出,actor_id和last_name,last_name等于個Joeexplain select actor_id,last_name from actor where last_name = 'Joe'\G
我們可以看到,在actor表中呢,雖然只有l(wèi)ast_name上有一個索引,但是同樣的我們在進行下面的查詢中,使用到了覆蓋查詢,覆蓋索引來進行查詢,是因為在innodb二級索引上呢,會自動加入我們的主鍵,actor_id的信息,同樣的我們可以通過last_name來進行覆蓋查詢,獲得我們要查詢的所有信息,還有一點大家要注意,對于不同版本的MYSQL呢,以下的輸出的執(zhí)行計劃可能不一樣,比如在MYSQL5.7中,在extra列中呢,只會出現(xiàn)using index這樣的信息,這可能更加符合我們對索引的理解,高版本的MYSQL性能優(yōu)化可能比低版本的MYSQL性能優(yōu)化可能會更好
?
總結(jié)
- 上一篇: Btree索引和Hash索引
- 下一篇: 索引优化策略(中)