sql索引優化


不啰嗦,直接入正題。問題是這樣的。請問下面的sql語句,要想加快查詢速度,該怎么創建索引?以下,以mysql數據庫為准。

 

select * from test where a=? and b>? order by c limit 0,100

 

結果可能會出乎你的意料。我們首先准備一下運行環境,然后按照最左前綴原則和explain關鍵字來進行驗證。結果真是顛覆了我多年的認知。

 

准備階段

 

為了進行驗證,我們創建一個簡單的數據表。里面有a、b、c三個簡單的int字段。

 

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進行驗證

 

1、自動選用索引

 

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么?

 

2、指定索引

 

接下來使用force index方式,強制指定索引。

 

這里直接給出結果,就是下面的sql。

 

explain select * from test
FORCE INDEX(idx_c_b_a) where a>10 and b >10 order by c

 

結果如下。

 

 

我們使用force index來指定使用的索引。這次效果非常好,顯示使用了index,使用了where,只在索引上就完成了操作。但掃描的行數卻增加了。

 

但是,這與我們的經驗是相悖的。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。其他索引都不是最優。

 

3、explain部分返回值意義

 

我們得出上面的結論,是根據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。

 

End

 

可以看到,在我們創建了多個索引的時候,mysql的查詢優化,並不一定能夠進行智能的解析、用到最優的方式,需要使用force index指定索引。

 

mysql中的索引,主要就用在where條件中和排序動作中。分兩種情況。

 

  1. 先過濾,再排序,會用到過濾條件中的索引參數,但是排序會使用較慢的外部排序。因為這個結果集是經過過濾的,並沒有什么索引參與。

  2. 先排序,再過濾,可以使用同一個索引,排序的優先級高於過濾的優先級。選擇合適的索引,在過濾的同時就把這個事給辦了。但是掃描的行數會增加。

 

我想,mysql並不能夠了解到這兩個過程,到底誰快誰慢,於是選了一個最通用的方式,直接選用了第一種。甚至在索引非常多的時候,直接暈菜了。索引建多了,你可能間接把mysql給害了這是現象,至於深層次的原因,歡迎讀過mysql相關源碼的給解釋一下。

 

這對經常變換字段進行排序的代碼來說,並不是一個好的信號。考慮到程序的穩定性,我想應該要盡量減少where條件過濾后的結果集。這種情況下,創建一個(a,b)的聯合索引,或許是一個折衷的方式。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM