原創:小姐姐味道(微信公衆號ID:xjjdog),歡迎分享,轉載請保留出處。mysql
掘金多能人,原理性內容可留言。程序員
不囉嗦,直接入正題。問題是這樣的。請問下面的sql語句,要想加快查詢速度,該怎麼建立索引?如下,以mysql數據庫爲準。sql
select * from test where a=? and b>? order by c limit 0,100
複製代碼
結果可能會出乎你的意料。咱們首先準備一下運行環境,而後按照最左前綴原則和explain關鍵字來進行驗證。結果然是顛覆了xjjdog多年的認知。數據庫
爲了進行驗證,咱們建立一個簡單的數據表。裏面有a、b、c三個簡單的int字段。bash
CREATE TABLE `test` (
`id` int(11) NOT NULL,
`a` int(11) DEFAULT NULL,
`b` int(11) DEFAULT NULL,
`c` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
複製代碼
接下來,寫一個簡單的存儲過程,來插入10w條數據。等待大約1分鐘,數據插入完畢。微信
DROP PROCEDURE IF EXISTS test_initData;
DELIMITER $
CREATE PROCEDURE test_initData()
BEGIN
DECLARE i INT DEFAULT 1;
WHILE i<=100000 DO
INSERT INTO test(id,a,b,c) VALUES(i,i*2,i*3,i*4);
SET i = i+1;
END WHILE;
END $
CALL test_initData();
複製代碼
因爲mysql有最左前綴原則,咱們對abc三列進行了全排列,建立了6個索引。這6個索引涵蓋了全部的根據abc查詢的狀況。架構
create INDEX idx_a_b_c on test(a,b,c);
create INDEX idx_a_c_b on test(a,c,b);
create INDEX idx_b_a_c on test(b,a,c);
create INDEX idx_b_c_a on test(b,c,a);
create INDEX idx_c_a_b on test(c,a,b);
create INDEX idx_c_b_a on test(c,b,a);
複製代碼
explain select * from test where a>10 and b >10 order by c
複製代碼
首先,咱們拿上面的sql語句進行驗證。結果發現,查詢使用了索引idx_a_b_c
,只用到了前綴a,b。而extra部分,則用到了filesort,也就是性能很是差的方式。併發
explain select * from test where c>10 and b >10 order by a
複製代碼
idx_b_a_c
,但依然使用的filesort,查詢效果是同樣的。按照上面的邏輯,不是應該選擇idx_b_c_a麼?
接下來使用force index方式,強制指定索引。 這裏直接給出結果,就是下面的sql。高併發
explain select * from test
FORCE INDEX(idx_c_b_a)
where a>10 and b >10 order by c
複製代碼
結果以下。工具
可是,這與咱們的經驗是相悖的。idx_c_b_a的索引,是在字段(c,b,a)上建立的。按照最左原則,支持的搜索條件有:c,cb,cba。在這個例子中,order by後面的參數,卻被看成了前綴的頭部信息。
咱們刪掉其餘索引,只留下idx_c_b_a,而後去掉force index部分。結果發現,mysql如今可以自動的選擇索引了。
在看另一種狀況,order by上有兩個參數。
explain select * from test
FORCE INDEX(idx_b_c_a)
where a>10 order by b,c
複製代碼
結果如上,使用idx_b_c_a,不走filesort。其餘索引都不是最優。
咱們得出上面的結論,是根據mysql本身提供的explain工具。這個工具可以輸出一些有用的信息。下面是相關的部分返回值的意義。
select_type
表示SELECT的類型,常見的取值有:
SIMPLE
簡單表,不使用錶鏈接或子查詢。
PRIMARY
主查詢,即外層的查詢。
UNION
UNION中的第二個或者後面的查詢語句。
SUBQUERY
子查詢中的第一個。
type
表示MySQL在表中找到所需行的方式,或者叫訪問類型。常見訪問類型以下,從下到上,性能愈來愈差。
system,const
表只有一行記錄(等於系統表),這是const類型的特列。
eq_ref
惟一性索引掃描,對於每一個索引鍵,表中只有一條記錄與之匹配。
ref
非惟一性索引掃描,返回匹配某個單獨值的全部行,本質上也是一種索引訪問,它返回全部匹配某個單獨值的行,然而,它可能會找到多個符合條件的行,因此他應該屬於查找和掃描的混合體。
range
只檢索給定範圍的行,使用一個索引來選擇行,key列顯示使用了哪一個索引。這種範圍掃描索引比全表掃描要好,由於它只須要開始於索引的某一點,而結束於另外一點,不用掃描所有索引。
index
Full Index Scan,Index與All區別爲index類型只遍歷索引樹。這一般比ALL快,由於索引文件一般比數據文件小。
all
全表掃描,性能最差
Extra
using index
表示相應的select操做中使用了覆蓋索引,避免訪問了表的數據行,效率不錯。若是同時出現using where,代表索引被用來執行索引鍵值的查找;若是沒有同時出現using where,代表索引用來讀取數據而非執行查找動做。
using filesort
說明mysql會對數據使用一個外部的索引排序,而不是按照表內的索引順序進行讀取。MySQL中沒法利用索引完成的排序操做稱爲「文件排序」。
using temporary
使用了用臨時表保存中間結果,mysql在對查詢結果排序時使用臨時表。常見於排序order by和分組查詢group by。
能夠看到,在咱們建立了多個索引的時候,mysql的查詢優化,並不必定可以進行智能的解析、用到最優的方式,須要使用force index指定索引。
mysql中的索引,主要就用在where條件中和排序動做中。分兩種狀況。
一、先過濾,再排序,會用到過濾條件中的索引參數,可是排序會使用較慢的外部排序。由於這個結果集是通過過濾的,並無什麼索引參與。
二、先排序,再過濾,可使用同一個索引,排序的優先級高於過濾的優先級。選擇合適的索引,在過濾的同時就把這個事給辦了。可是掃描的行數會增長。
我想,mysql並不可以瞭解到這兩個過程,到底誰快誰慢,因而選了一個最通用的方式,直接選用了第一種。甚至在索引很是多的時候,直接暈菜了。**索引建多了,你可能間接把mysql給害了。**這是現象,至於深層次的緣由,歡迎讀過mysql相關源碼的給解釋一下。
這對常常變換字段進行排序的代碼來講,並非一個好的信號。考慮到程序的穩定性,我想應該要儘可能減小where條件過濾後的結果集。這種狀況下,建立一個(a,b)的聯合索引,或許是一個折衷
的方式。
做者簡介:小姐姐味道 (xjjdog),一個不容許程序員走彎路的公衆號。聚焦基礎架構和Linux。十年架構,日百億流量,與你探討高併發世界,給你不同的味道。個人我的微信xjjdog0,歡迎添加好友,進一步交流。