1. 程式人生 > 資料庫 >MySQL的索引詳解

MySQL的索引詳解

一. 索引基礎

1.1 簡介

在MySQL中,索引(index)也叫做“鍵(key)”,它是儲存引擎用於快速找到記錄的一種資料結構。

索引對於良好的效能非常關鍵,尤其是當表中的資料量越來越大時,索引對效能的影響就愈發重要。

索引優化應該是對查詢效能優化最有效的手段,建立一個真正最優的索引經常需要重寫SQL查詢語句。

1.2 索引的工作原理

要理解MySQL中索引的工作原理,最簡單的方法就是去看一看一本書的索引部分:比如你想在一本書中尋找某個主題,一般會先看書的索引目錄,找到對應的章節、對應的頁碼後就可以快速找到你想看的內容。

在MySQL中,儲存引擎用類似的方法使用索引,其先在索引中查詢對應的值,然後根據匹配的索引記錄找到對應的資料行,最後將資料結果集返回給客戶端。

1.3 索引的型別

在MySQL中,通常我們所指的索引型別,有以下幾種:

  • 常規索引

    常規索引,也叫普通索引(index或key),它可以常規地提高查詢效率。一張資料表中可以有多個常規索引。常規索引是使用最普遍的索引型別,如果沒有明確指明索引的型別,我們所說的索引都是指常規索引。

  • 主鍵索引

    主鍵索引(Primary Key),也簡稱主鍵。它可以提高查詢效率,並提供唯一性約束。一張表中只能有一個主鍵。被標誌為自動增長的欄位一定是主鍵,但主鍵不一定是自動增長。一般把主鍵定義在無意義的欄位上(如:編號),主鍵的資料型別最好是數值。

  • 唯一索引

    唯一索引(Unique Key),可以提高查詢效率,並提供唯一性約束。一張表中可以有多個唯一索引。

  • 全文索引

    全文索引(Full Text),可以提高全文搜尋的查詢效率,一般使用Sphinx替代。但Sphinx不支援中文檢索,Coreseek是支援中文的全文檢索引擎,也稱作具有中文分詞功能的Sphinx。實際專案中,我們用到的是Coreseek。

  • 外來鍵索引

    外來鍵索引(Foreign Key),簡稱外來鍵,它可以提高查詢效率,外來鍵會自動和對應的其他表的主鍵關聯。外來鍵的主要作用是保證記錄的一致性和完整性。

    注意:只有InnoDB儲存引擎的表才支援外來鍵。外來鍵欄位如果沒有指定索引名稱,會自動生成。如果要刪除父表(如分類表)中的記錄,必須先刪除子表(帶外來鍵的表,如文章表)中的相應記錄,否則會出錯。 建立表的時候,可以給欄位設定外來鍵,如 foreign key(cate_id) references cms_cate(id),由於外來鍵的效率並不是很好,因此並不推薦使用外來鍵,但我們要使用外來鍵的思想來保證資料的一致性和完整性。

1.4 索引的方法

在MySQL中,索引是在儲存引擎層實現的,而不是在伺服器層。MySQL支援的索引方法,也可以說成是索引的型別(這是廣義層面上的),主要有以下幾種:

B-Tree 索引

如果沒有特別指明型別,那多半說的就是B-Tree 索引。不同的儲存引擎以不同的方式使用B-Tree索引,效能也各不相同。例如:MyISAM使用字首壓縮技術使得索引更小,但InnoDB則按照原始的資料格式儲存索引。再如MyISAM通過資料的物理位置引用被索引的行,而InnoDB則根據主鍵引用被索引的行。

B-Tree 對索引列是順序儲存的,因此很適合查詢範圍資料。它能夠加快訪問資料的速度,因為儲存引擎不再需要進行全表掃描來獲取需要的資料。

如果一個索引中包括多個欄位(列)的值,那它就是一個複合索引。複合索引對多個欄位值進行排序的依據是建立索引時列的順序。如下:

create table people (
 id int unsigned not null auto_increment primary key comment '主鍵id',last_name varchar(20) not null default '' comment '姓',first_name varchar(20) not null default '' comment '名',birthday date not null default '1970-01-01' comment '出生日期',gender tinyint unsigned not null default 3 comment '性別:1男,2女,3未知',key(last_name,first_name,birthday)
) engine=innodb default charset=utf8;

people表中也已經插入瞭如下一些資料:

id last_name first_name birthday gender
1 Clinton Bill 1970-01-01 3
2 Allen Cuba 1960-01-01 3
3 Bush George 1970-01-01 3
4 Smith Kim 1970-01-01 3
5 Allen Cally 1989-06-08 3

我們建立了一個複合索引 key(last_name,birthday),對於表中的每一行資料,該索引中都包含了姓、名和出生日期這三列的值。索引也是根據這個順序來排序儲存的,如果某兩個人的姓和名都一樣,就會根據他們的出生日期來對索引排序儲存。

B-Tree 索引適用於全鍵值、鍵值範圍或鍵字首查詢,其中鍵字首查詢只適用於根據最左字首查詢。

複合索引對如下型別的查詢有效:

全值匹配

全值匹配指的是和索引中的所有列進行匹配。例如:查詢姓Allen、名Cuba、出生日期為1960-01-01的人。

SQL語句為:

select id,last_name,birthday from people where last_name='Allen' and first_name='Cuba' and birthday='1960-01-01';

匹配最左字首

比如只使用索引的第一列,查詢所有姓為Allen的人。SQL語句為:

select id,birthday from people where last_name='Allen';

匹配列字首

比如只匹配索引的第一列的值的開頭部分,查詢所有姓氏以A開頭的人。SQL語句為:

select id,birthday from people where last_name like ‘A%';

匹配範圍值

比如範圍匹配姓氏在Allen和Clinton之間的人。SQL語句為:

select id,birthday from people where last_name BETWEEN ‘Allen' And ‘Clinton';

這裡也只使用了索引的第一列。

精確匹配第一列並範圍匹配後面的列

比如查詢姓Allen,並且名字以字母C開頭的人。即全匹配複合索引的第一列,範圍匹配第二列。SQL語句為:

select id,birthday from people where last_name = ‘Allen' and first_name like'C%';

只訪問索引的查詢

B-Tree 通常可以支援“只訪問索引的查詢”,即查詢只需要訪問索引,而無需訪問資料行。這和“覆蓋索引”的優化相關,後面再講。

下面介紹一些複合索引會失效的情況:

(1)如果不是按照複合索引的最左列開始查詢,則無法使用索引。例如:上面的例子中,索引無法用於查詢查詢名為Cuba的人,也無法查詢某個特定出生日期的人,因為這兩列都不是複合索引 key(last_name,birthday) 的最左資料列。類似地,也無法查詢姓氏以某個字母結尾的人,即like範圍查詢的模糊匹配符%,如果放在第一位會使索引失效。

(2)如果查詢時跳過了索引中的列,則只有前面的索引列會用到,後面的索引列會失效。比如查詢姓Allen且出生日期在某個特定日期的人。這裡查詢時,由於沒有指定查詢名(first_name),故MySQL只能使用該複合索引的第一列(即last_name)。

(3)如果查詢中有某個列的範圍查詢,則該列右邊的所有列都無法使用索引優化查詢。例如有查詢條件為 where last_name='Allen' and first_name like ‘C%' and birthday='1992-10-25',這個查詢只能使用索引的前兩列,因為這裡的 like 是一個範圍條件。假如,範圍查詢的列的值的數量有限,那麼可以通過使用多個等於條件代替範圍條件進行優化,來使右邊的列也可以用到索引。

現在,我們知道了複合索引中列的順序是多麼的重要,這些限制都和索引列的順序有關。在優化效能的時候,可能需要使用相同的列但順序不同的索引來滿足不同型別的查詢需求,比如在一張表中,可能需要兩個複合索引 key(last_name,birthday) 和 key(first_name,birthday) 。

B-Tree索引是最常用的索引型別,後面,如果沒有特別說明,都是指的B-Tree索引。

1、雜湊索引

雜湊索引(hash index)基於雜湊表實現,只有精確匹配索引所有列的查詢才有效。在MySQL中,只有Memory引擎顯示支援雜湊索引。

2、空間資料索引(R-Tree)

MyISAM引擎支援空間索引,可以用作地理資料儲存。和B-Tree索引不同,該索引無須字首查詢。

3、全文索引

全文索引是一種特殊型別的索引,它查詢的是文字中的關鍵詞,而不是直接比較索引中的值。全文索引和其他幾種索引的匹配方式完全不一樣,它更類似於搜尋引擎做的事情,而不是簡單的where條件匹配。可以在相同的列上,同時建立全文索引和B-Tree索引,全文索引適用於 Match Against 操作,而不是普通的where條件操作。

索引可以包含一個列(即欄位)或多個列的值。如果索引包含多個列,一般會將其稱作複合索引,此時,列的順序就十分重要,因為MySQL只能高效的使用索引的最左字首列。建立一個包含兩個列的索引,和建立兩個只包含一列的索引是大不相同的。

1.5 索引的優點

索引可以讓MySQL快速地查詢到我們所需要的資料,但這並不是索引的唯一作用。

最常見的B-Tree索引,按照順序儲存資料,所以,MySQL可以用來做Order By和Group By操作。因為資料是有序儲存的,B-Tree也就會把相關的列值都儲存在一起。最後,因為索引中也儲存了實際的列值,所以某些查詢只使用索引就能夠獲取到全部的資料,無需再回表查詢。據此特性,總結出索引有如下三個優點:

  • 索引大大減少了MySQL伺服器需要掃描的資料量。
  • 索引可以幫助伺服器避免排序和臨時表。
  • 索引可以將隨機I/O變為順序I/O。

此外,有人用“三星系統”(three-star system)來評價一個索引是否適合某個查詢語句。三星系統主要是指:如果索引能夠將相關的記錄放到一起就獲得一星;如果索引中的資料順序和查詢中的排列順序一致就獲得二星;如果索引中的列包含了查詢需要的全部列就獲得三星。

索引並不總是最好的工具,也不是說索引越多越好。總的來說,只要當索引幫助儲存引擎快速找到記錄帶來的好處大於其帶來的額外工作時,索引才是有用的。

對於非常小的表,大部分情況下簡單的全表掃描更高效,沒有必要再建立索引。對於中到大型的表,索引帶來的好處就非常明顯了。

二. 高效能的索引策略

正確地建立和使用索引是實現高效能查詢的基礎。前面,已經介紹了各種型別的索引及其優缺點,現在來看看如何真正地發揮這些索引的優勢。下面的幾個小節將幫助大家理解如何高效地使用索引。

2.1 獨立的列

我們通常會看到一些查詢不當地使用索引,或者使得MySQL無法使用已有的索引。如果SQL查詢語句中的列不是獨立的,則MySQL就不會使用到索引。“獨立的列”是指索引列不能是表示式的一部分,也不能是函式的引數。

例如:下面這條SQL查詢語句,就無法使用主鍵索引id:

select id,birthday from people where id+1=3;

很容易看出,上面的where表示式其實可以簡寫為 where id=2,但是MySQL無法自動解析這個表示式。我們應該養成簡化where條件的習慣,始終將索引列單獨放在比較運算子的一側。故要想使用到主鍵索引,正確地寫法為:

select id,birthday from people where id=2;

下面是另一個常見的錯誤寫法:

select ... from ... where to_days(current_date()) - to_days(date_col) <= 10;

2.2 字首索引和索引的選擇性

有時候,我們需要索引很長的字元列,這會讓索引變得大且慢。通常的解決方法是,只索引列的前面幾個字元,這樣可以大大節約索引空間,從而提高索引的效率。但是,也會降低索引的選擇性。索引的選擇性是指,不重複的索引值的數目(也稱為基數)與資料表中的記錄總數的比值,取值範圍是0到1。

唯一索引的選擇性是1,這是最好的索引選擇性,效能也是最好的。

一般情況下,某個列字首的選擇性也是足夠高的,足以滿足查詢效能。對於Blob、Text或很長的Varchar型別的列,必須使用字首索引,即只對列的前面幾個字元進行索引,因為MySQL不允許索引這些列的完整長度。

新增字首索引的方法如下:

alter table user add key(address(8)); // 只索引address欄位的前8個字元

字首索引是一種能使索引更小、更快的有效辦法,但缺點是:MySQL無法使用字首索引做 Order By 和 Group By 操作,也無法使用字首索引做覆蓋掃描。

有時,字尾索引(suffix index)也有用途,例如查詢某個域名的所有電子郵件地址。但MySQL原生並不支援字尾索引,我們可以把字串反轉後儲存,並基於此建立字首索引,然後通過觸發器來維護這種索引。

2.3 多列索引

多列索引是指一個索引中包含多個列,必須要注意多個列的順序。多列索引也叫複合索引,如前面的 key(last_name,birthday) 就是一個複合索引。

一個常見的錯誤就是,為每個列建立單獨的索引,或者,按照錯誤的順序建立了多列索引。

先來看第一個問題,為每個列建立獨立的索引,從 show create table 中,很容易看到這種情況:

create table t (
 c1 int,c2 int,c3 int,key(c1),key(c2),key(c3)
);

這種錯誤的索引策略,一般是由於人們聽到一些專家諸如“把where條件裡面的列都加上索引”這樣模糊的建議導致的。

在多個列上建立獨立的單列索引大部分情況下並不能提高MySQL的查詢效能。在MySQL 5.0及以後的版本中,引入了一種叫索引合併(index merge)的策略,它在一定程度上可以使用表上的多個單列索引來定位指定的行。但效率還是比複合索引差很多。

例如:表 film_actor 在欄位 film_id 和 actor_id 上各有一個單列索引,SQL查詢語句如下:

select film_id,actor_id from film_actor where actor_id=1 or film_id=1;

在MySQL5.0以後的版本中,查詢能夠同時使用這兩個單列索引進行掃描,並將結果進行合併。這種演算法有三個變種:or條件的聯合(union)、and條件的相交(intersection)、組合前兩種情況的聯合及相交。

上面的查詢就是使用了兩個索引掃描的聯合,通過explain中的Extra列(Extra的值中會出現union字元),可以看出這一點:

explain select film_id,actor_id from film_actor where actor_id=1 or film_id=1\G

索引合併策略有時候是一種優化的結果,但實際上更多時候它說明了表上的索引建得很糟:

  • 當出現對多個索引做相交操作時(通常有多個and條件),通常意味著需要一個包含所有相關列的複合索引,而不是多個獨立的單列索引。
  • 當出現對多個索引做聯合操作時(通常有多個or條件),通常需要消耗大量的CPU和記憶體資源在演算法的快取、排序和合並操作上。此時,可以將查詢改寫成兩個查詢Union的方式:

select film_id,actor_id from film_actor where actor_id=1
union all
select film_id,actor_id from film_actor where film_id=1 and actor_id<>1;

如果在explain的結果中,發現了索引的聯合,應該好好檢查一下SQL查詢語句和表的結構,看是不是已經是最優的了,能否將其拆分為多個查詢Union的方式等等。

2.4 選擇合適的索引列順序

最容易引起困惑的就是複合索引中列的順序。在複合索引中,正確地列順序依賴於使用該索引的查詢,並且同時需要考慮如何更好地滿足排序和分組的需要。

索引列的順序意味著索引首先按照最左列進行排序,其次是第二列,第三列…。所以,索引可以按照升序或者降序進行掃描,以滿足精確符合列順序的order by、group by和distinct等子句的查詢需求。

當不需要考慮排序和分組時,將選擇性最高的列放到複合索引的最左側(最前列)通常是很好的。這時,索引的作用只是用於優化where條件的查詢。但是,可能我們也需要根據那些執行頻率最高的查詢來調整索引列的順序,讓這種情況下索引的選擇性最高。

以下面的查詢為例:

select * from payment where staff_id=2 and customer_id=500;

是應該建立一個 key(staff_id,customer_id) 的索引還是 key(customer_id,staff_id) 的索引?可以跑一些查詢來確定表中值的分佈情況,並確定哪個列的選擇性更高。比如:可以用下面的查詢來預測一下:

select sum(staff_id=2),sum(customer_id=500) from payment\G

假如,結果顯示:sum(staff_id=2)的值為7000,而sum(customer_id=500)的值為60。由此可知,在上面的查詢中,customer_id的選擇性更高,應該將其放在索引的最前面,也就是使用key(customer_id,staff_id) 。

但是,這樣做有一個地方需要注意,查詢的結果非常依賴於選定的具體值。如果按照上述方法優化,可能對其他不同條件值的查詢不公平,也可能導致伺服器的整體效能變得更糟。

如果是從pt-query-digest這樣的工具的報告中提取“最差查詢”,再按上述辦法選定的索引順序往往是非常高效的。假如,沒有類似地具體查詢來執行,那麼最好還是根據經驗法則來做,因為經驗法則考慮的是全域性基數和選擇性,而不是某個具體條件值的查詢。通過經驗法則,判斷選擇性的方法如下:

select count(distinct staff_id)/count(*) as staff_id_selectivity,count(distinct customer_id)/count(*) as customer_id_selectivity,from payment\G

假如,結果顯示:staff_id_selectivity的值為0.001,而customer_id_selectivity的值為0.086。我們知道,值越大,選擇性越高。故customer_id的選擇性更高。因此,還是將其作為索引列的第一列:

alter table payment add key(customer_id,staff_id);

儘管,關於選擇性和全域性基數的經驗法則值得去研究和分析,但一定別忘了order by、group by 等因素的影響,這些因素可能對查詢的效能造成非常大的影響。

2.5 聚簇索引

聚簇索引並不是一種單獨的索引型別,而是一種資料儲存方式。具體的細節依賴於其實現方式,但InnoDB 的聚簇索引實際上在同一結構中儲存了 B-Tree 索引和資料行。

當表中有聚簇索引時,它的資料行實際上存放在索引的葉子頁(leaf page)中,也就是說,葉子頁包含了行的全部資料,而節點頁只包含了索引列的資料。

因為是儲存引擎負責實現索引,因此並不是所有的儲存引擎都支援聚簇索引。本節我們主要關注InnoDB,這裡討論的內容對於任何支援聚簇索引的儲存引擎都是適用的。

InnoDB 通過主鍵聚集資料,如果沒有定義主鍵,InnoDB 會選擇一個唯一的非空索引代替。如果沒有這樣的索引,InnoDB 會隱式定義一個主鍵來作為聚簇索引。

聚簇索引的優點:

  • 可以把相關的資料儲存在一起。
  • 資料訪問更快。聚簇索引將索引和資料儲存在同一個B-Tree中,因此,從聚簇索引中獲取資料通常比非聚簇索引要快。
  • 使用覆蓋索引掃描的查詢可以直接使用節點頁中的主鍵值。

如果在設計表和查詢時,能充分利用上面的優點,就可以極大地提升效能。

聚簇索引的缺點:

  • 聚簇索引最大限度地提高了I/O密集型應用的效能,但如果資料全部放在記憶體中,則訪問的順序就沒那麼重要了,聚簇索引也就沒什麼優勢了。
  • 插入速度嚴重依賴於插入順序。按照主鍵的順序插入是插入資料到InnoDB表中速度最快的方式。但如果不是按照主鍵順序插入資料,那麼,在操作完畢後,最好使用 OPTIMIZE TABLE 命令重新組織一下表。
  • 更新聚簇索引列的代價很高,因為會強制InnoDB將每個被更新的行移動到新的位置。
  • 基於聚簇索引的表在插入新行,或者主鍵被更新,導致需要移動行的時候,可能面臨“頁分裂(page split)”的問題。頁分裂會導致表佔用更多的磁碟空間。

在InnoDB中,聚簇索引“就是”表,所以不像MyISAM那樣需要獨立的行儲存。聚簇索引的每一個葉子節點都包含了主鍵值、事務ID、用於事務和MVCC(多版本控制)的回滾指標以及所有的剩餘列。

InnoDB的二級索引(非聚簇索引)和聚簇索引差別很大,二級索引的葉子節點中儲存的不是“行指標”,而是主鍵值。故通過二級索引查詢資料時,會進行兩次索引查詢。儲存引擎需要先查詢二級索引的葉子節點來獲得對應的主鍵值,然後根據這個主鍵值到聚簇索引中查詢對應的資料行。

為了保證資料行按順序插入,最簡單的方法是將主鍵定義為 auto_increment 自動增長。使用InnoDB時,應該儘可能地按主鍵順序插入資料,並且儘可能地使用單調增加的主鍵值來插入新行。

對於高併發工作負載,在InnoDB中按主鍵順序插入可能會造成明顯的主鍵值爭用的問題。這個問題非常嚴重,可自行百度解決。

2.6 覆蓋索引

通常大家都會根據查詢的where條件來建立合適的索引,但這只是索引優化的一個方面。設計優秀的索引,應該考慮整個查詢,而不單單是where條件部分。

索引確實是一種查詢資料的高效方式,但是MySQL也可以使用索引來直接獲取列的資料,這樣就不必再去讀取資料行。如果索引的葉子節點中已經包含了要查詢的全部資料,那麼,還有什麼必要再回表查詢呢?

如果一個索引包含(或者覆蓋)了所有需要查詢的欄位(列)的值,我們稱之為“覆蓋索引”。

覆蓋索引是非常有用的,能夠極大地提高效能。考慮一下,如果查詢只需要掃描索引,而無須回表獲取資料行,會帶來多少好處:

  • 索引條目通常遠小於資料行大小,所以如果只需要讀取索引,那MySQL就會極大地減少資料訪問量。覆蓋索引對I/O密集型的應用也有幫助,因為索引比資料更小,更容易全部放入記憶體中。
  • 因為索引是按照列值順序儲存的(至少在單個頁內是這樣),所以對於I/O密集型的範圍查詢比隨機從磁碟讀取每一行的資料I/O要少得多。
  • 由於InnoDB的聚簇索引,覆蓋索引對InnoDB表特別有用。InnoDB的二級索引(非聚簇索引)在葉子節點中儲存了行的主鍵值,所以如果二級主鍵能夠覆蓋查詢,則可以避免對主鍵索引的二次查詢。

在所有這些場景中,在索引中就完成所有查詢的成本一般比再回表查詢小得多。

B-Tree索引可以成為覆蓋索引,但雜湊索引、空間索引和全文索引等均不支援覆蓋索引。

當發起一個被索引覆蓋的查詢(也叫做索引覆蓋查詢)時,在 explain 的 Extra 列,可以看到 “Using index” 的資訊。如:

explain select id from people;
explain select last_name from people;
explain select id,first_name from people;
explain select last_name,birthday from people;
explain select last_name,birthday from people where last_name='Allen';

people表是我們在上面的小節中建立的,它包含一個主鍵(id)索引和一個多列的複合索引key(last_name,birthday),這兩個索引覆蓋了四個欄位的值。如果一個SQL查詢語句,要查詢的欄位都在這四個欄位之中,那麼,這個查詢就可以被稱為索引覆蓋查詢。如果一個索引包含了某個SQL查詢語句中所有要查詢的欄位的值,這個索引對於該查詢語句來說,就是一個覆蓋索引。例如,key(last_name,birthday) 對於 select last_name,first_name from people 就是覆蓋索引。

2.7 使用索引掃描來做排序

MySQL有兩種方式可以生成有序的結果集:通過排序操作(order by)和 按索引順序掃描的自動排序(即通過索引來排序)。其實,這兩種排序操作是不衝突的,也就是說 order by 可以使用索引來排序。

確切地說,MySQL的對結果集的排序方式有下面兩種:

1、索引排序

索引排序是指使用索引中的欄位值對結果集進行排序。如果explain出來的type引數的值為index,就說明MySQL一定使用了索引排序。如:

explain select id from people;
explain select id,last_name from people order by id desc;
explain select last_name from people;
explain select last_name from people order by last_name;
explain select last_name from people order by last_name desc;

注意:就算explain出來的type的值不是index,也有可能是索引排序。如:

explain select id from people where id >3;
explain select id,last_name from people where id >3 order by id desc;

2、檔案排序

檔案排序(filesort)是指將查詢出來的結果集通過額外的操作進行排序,然後返回給客戶端。這種排序方式,沒有使用到索引排序,效率較低。雖然檔案排序,MySQL將其稱為filesort,但並不一定使用磁碟檔案。

如果explain出來的Extra引數的值包含“Using filesort”字串,就說明是檔案排序。此時,你就必須對索引或SQL查詢語句進行優化了。如:

explain select id,first_name from people where id > 3 order by last_name;

MySQL可以使用同一個索引既滿足查詢,又滿足查詢。如果可能,設計索引時,應該儘可能地同時滿足這兩種操作。

只有當索引的列包含where條件中的欄位和order by中的欄位,且索引中列的順序和where + order by 中包含的所有欄位的順序一致(注意:order by在where的後面)時,才有可能使用到索引排序。

現在,我們來優化上面的那條SQL語句,使其利用索引排序。

首先,新增一個多列索引。

alter table people add key(id,last_name);

會發現,僅新增 key(id,last_name),還是沒辦法使用索引排序,這是因為,where + order by 語句也要滿足索引的最左字首要求,而where id > 3是一個範圍條件,會導致後面的order by last_name無法使用索引key(id,last_name)。

其次,將SQL語句中的 order by last_name 改為 order by id,last_name。

注意:如果SQL查詢語句是一個關聯多張表的關聯查詢,則只有當order by排序的欄位全部來自於第一張表時,才能使用索引排序。

下面列出幾種不能使用索引排序的情況:

1、如果order by根據多個欄位排序,但多個欄位的排序方向不一致,即有的欄位是asc(升序,預設是升序),有的欄位是desc(降序)。如:

explain select * from people where last_name='Allen' order by first_name asc,birthday desc;

2、如果order by包含了一個不在索引列的欄位。如:

explain select * from people where last_name='Allen' order by first_name,gender;

3、如果索引列的第一列是一個範圍查詢條件。如:

explain select * from people where last_name like 'A%' order by first_name;

4、對於這種情況,可以將SQL語句優化為:

explain select * from people where last_name like 'A%' order by last_name,first_name;

2.8 冗餘和重複索引

MySQL允許在相同的列上建立多個索引(只不過索引的名稱不同),由於MySQL需要單獨維護重複的索引,並且優化器在優化查詢時也需要逐個地進行分析考慮,故重複的索引會影響效能。

重複索引是指在相同的列上按照相同的列順序建立的型別相同的索引。應該避免建立重複索引,發現以後也應立即刪除。

冗餘索引和重複索引不同。如果建立了索引 key(A,B),再來建立索引 key(A),就是冗餘索引。因為索引(A)只是前一個索引的字首索引。索引(A,B)也可以當做索引(A)來使用。但是,如果再建立索引(B,A),就不是冗餘索引了。

冗餘索引通常發生在為表新增新索引的時候。例如,有人可能會增加一個新的索引(A,B),而不是擴充套件已有的索引(A)。還有一種情況是,將一個二級索引(A)擴充套件為(A,ID),其中ID是主鍵,對於InnoDB來說,二級索引中已經預設包含了主鍵列,所以這也是冗餘的。

大多數情況下,都不需要冗餘索引。應該儘量擴充套件已有的索引而不是建立新索引。但有時,出於效能方面的考慮,也需要冗餘索引,因為擴充套件已有的索引會導致其變大,從而會影響其他使用該索引的查詢語句的效能。

在擴充套件索引的時候,需要特別小心。因為二級索引的葉子節點包含了主鍵值,所以在列(A)上的索引就相當於在(A,ID)上的索引。如果有人用了像 where A=5 order by ID 這樣的查詢,索引(A)就非常有用。但是,如果你將索引(A)修改為索引(A,B),則實際上就變成了索引(A,B,ID),那麼,上面查詢的order by語句就無法使用索引排序,而只能使用檔案排序了。

推薦使用Percona工具箱中的pt-upgrade工具來仔細檢查計劃中的索引變更。

因此,只有當你對一個索引相關的所有查詢都很清楚時,才去擴充套件原有的索引。否則,建立一個新的索引(讓原有索引成為新索引的冗餘索引)才是最保險的方法。

2.9 未使用的索引

MySQL伺服器中可能會有一些永遠都不會用到的索引,這樣的索引完全是累贅,建議考慮刪除。但要注意的是,唯一索引的唯一性約束功能,可能某個唯一索引一直沒有被查詢使用,卻能用於避免產生重複的資料。