MySQL(四)——SQL常見優化手段及如何避免索引失效

MySQL(三)——SQL執行計劃分析這篇文章中具體介紹了一下SQL執行計劃中各個字段的含義,今天這篇文章就簡單的說一說具體的優化手段吧。

一、SQL優化

在做具體的總結之前,爲了便於理解,先拿一個具體的例子的簡單的實際操作一下具體的SQL優化過程。

先創建一張book表(有圖書編號、書名、作者、出版編號、類型編號四個字段),並隨便插入幾條數據:

create table book(
    bid int(4) primary key,
    bname varchar(20),
    authorid int(4),
    publishid int(4),
    typeid int(4)
);
insert into book values(1,'java',1,1,2);
insert into book values(2,'SQL',2,1,2);
insert into book values(3,'python',3,2,1);
insert into book values(4,'PHP',4,2,3);
commit;

有了數據後我們做一個查詢:查詢類型編號(typeid)爲2或3,作者編號(authorid)爲1的圖書編號(bid),並按類型標號bypeid降序排列。對應的SQL:select bid from book where typeid in(2,3) and authorid=1 order by typeid desc;我們看一下這個SQL的執行計劃:

從上面可以看出來,這個SQL語句的性能還是很差的,沒有用到任何索引(當然因爲我們還沒創建),type類型爲all,Extral爲using filesort。下面我們一步步來進行優化:

優化一:加索引

現在爲我們上面用到的三個字段加上索引(複合索引):alter table book add index bid_typeid_authorid_index(bid,typeid,authorid);加索引後看一下上面SQL的執行計劃:可以看到using filesort依然存在,但type由原來的all降到了現在的index,整體比上面的好一些,但還不是很好。

優化二:根據SQL語句的解析順序,調整索引順序

我們在前面SQL執行計劃裏提到,MySQL對SQL語句的解析是有順序的,它不會按着你編寫的SQL順序解析,而是按照如下順序解析:

from->on->join->where->gruop by->having->select (dinstinct)->order by->limit

由上面的解析順序可以知道,MySQL是先解析where後面的typeid字段和authorid字段,最後才解析select後面的bid字段,而我們在第一步的優化中建立的複合索引順序卻是bid在最前,authorid在最後(bid,typeid,authorid),現在我們根據mysql的解析順序調整複合索引的順序,讓他們二者保持一致:alter table book add index typeid_authorid_bid_index(typeid,authorid,bid);可以看到using filesort被消除了。

這裏需要注意一點,我們在第二步優化的時候重現創建了索引,第一步中的索引還在。在實際開發中如果我們確定第一個索引不會用了,應當把它刪除掉,因爲有時候舊的索引會干擾我們優化。舉個簡單的例子,比如第一次創建了一個複合索引(a,b,c)覺得不合適,在沒有刪除它的前提下又創建了另一索引(a,b),這個時候如果再進行優化的時候可能用到的還是之前的(a,b,c)索引,而新的(a,b)沒有用到。

在此刪除第一步中的索引:drop index bid_typeid_authorid_index on book;

優化三:調整原來的SQL語句順序,並按調整後的順序重新創建新的索引

原來SQL語句:select bid from book where typeid in(2,3) and authorid=1 order by typeid desc;

調整後SQL語句select bid from book where authorid=1 and typeid in(2,3) order by typeid desc;

刪除第二步中的索引:drop index typeid_authorid_bid_index on book;

根據調整後的SQL語句順序重新創建新的索引:alter table book add index authorid_typeid_bid_index(authorid,typeid,bid);

可以看到上面的兩個SQL語句僅僅是把where後面的typeid字段和authorid字段換了一下順序,爲什麼要這麼做?這個其實我們在MySQL(三)——SQL執行計劃分析中也提到過,就是in的範圍查詢有時候會使索引失效。比如原來SQL中where條件是這樣的:where typeid in(2,3) and authorid=1,這個時候因爲in的存在可能會導致typeid這個索引失效,而在複合索引中如果一個索引失效,那麼它後面的索引也會自動失效,這個時候原來的索引(typeid,authorid,bid)中,後面的authorid和bid也會自動失效。所以爲了避免這種情況,我們把順序調整一下,並根據調整後的順序重新建立新的索引。下面看一下調整後的執行計劃,可以看到type類型由原來的的index提升到了現在的ref。

優化小結:上面只是一個簡單的優化案例,在實際開發中對優化也是這麼一步步來的,不可一步到位。現在簡單總結一下實際開發中常用到的一些優化手段

    1.最佳左前綴原則:即保持索引的定義順序和使用的順序(where後面的字段順序)一致。比如定義了一個複合索引(a,b,c)那麼在具體的SQL語句中最好按select...from A where a..b...c..這個順序來,而不要按這種select...from A where b...c..a...無序的來,或者這種select...from A where a...c...跨列的方式來。

    2.將含有in的範圍查詢放到where條件的最後,防止讓本身索引失效外,引起後面的索引也失效。這一點就是我們在上面案例中“優化三”中說道的情況。

    3.關於order by的優化:對於與order by處理不當經常會導致Extral中usring filesort的情況發生。其實在MySQL中關於排序它的底層有兩種排序方法,mysql4.1之前一直是雙路排序,4.1之後改爲了單路排序,單雙路排序的劃分是按照mysql掃描磁盤的次數(I/O次數)進行的。那個具體的SQL語句說明,比如select name,age from A  order by id

        雙路排序:會掃描兩次磁盤,第一次從磁盤掃描排序字段id,第二次掃描其他字段 name和age

        單路排序:因爲IO比較消耗性能能,所以MySQL爲了減少IO次數,在4.1之後改爲單路。只讀取一次磁盤,它會一次性把name,age,id三個字段都掃描完。

不管是單路排序還是雙路排序,MySQL在拿到字段後(對於雙路排序只拿排序字段,對於單路排序拿全部查詢的字段)都會把它們放到MySQL內部自帶的的一個buffer緩存中,然後對他們進行排序。所以單路排序會比雙路排序佔用更多的buffer空間,單路排序如果在使用時數據量比較大,可以考慮調大buffer的容量(set max_length_for sort data=具體的字節數)。對於單路排序如果需要排序的列數大小超過了max_length_for sort data設定的大小,MySQL會自動從單路切換到雙路。

根據以上情況,針對order by的優化手段主要有:

        3.1.根據實際情況,適當調整buffer的容量大小

        3.2.儘量保持排序的一致性(要麼都是升序、要麼都是降序),避免order by  a desc,c asc這種按a降序的同時又按c升序

        3.3.對於符合索引,SQL中where和order by拼接起來的字段,不要跨列使用。比如複合索引爲(a,b,c),那麼像這種select...from...where a=..b=..order by c;甚至select...from...where a=..c=..order by b;都沒什麼問題,因爲不管where後面是a,b還是a,c,最後和order by後面的字段拼接後都能組成有序的a,b,c序列(對於第二種情況mysql中的優化引擎會自動幫我們調整好順序),保持了和複合索引(a,b,c)順序的一致性。但如果是這種select...from...where a=.order by c;SQL語句,就屬於跨列了(把b跨了過去),性能肯定會變差。

    4.避免*的使用,假如一個表有3列(id,name,age),我們儘量使用select id,name,age from.. 而不是select * from...

    5.關於exists和in的優化使用:如果主查詢的數據量大,使用in;如果子查詢的數據量大,則使用exists

        select..from table where exists (子查詢)

        select..from table where 某個字段 in (子查詢)

        可能有的同學對於exists和in不是太熟悉,這裏簡單說明一下:

        exists/in就是將主查詢的結果放到子查詢結果中進行條件校驗,看子查詢中是否有數據,如果符合校驗則保留查詢數據,比如下面的兩條SQL,第一條表示先從A表中查到所有name信息,然後再和B表中的name信息對比,如果發現B表中也有這些name信息,則返回查詢結果。

        select name from A where  exists (select name from B )

        select * from A where A.id in (select B.id from B);

    6.對於雙表或者多表查詢,准許“小表驅動大表”的原則。因爲雙表就涉及到兩張表了,那麼我們在where條件中一般把數據量小的表放到左邊,數據量大的表放到右邊,像這樣:where 小表.id=大表.id,這就是所謂的小表驅動大表的意思。那麼在加索引的時候,一般更多的也是選擇給小表中的字段加。

    7.對於連接查詢,一般如果是左連接就給左表字段加索引,右連接就給右表字段加索引。

以上只是一般的幾個常見優化手段,對於特殊情況我們需要根據具體的業務適當變動,比如對於第6條,我們在給小表中字段加索引的同時,有時候也需要給大表中的某些字段加適當的索引;對於第7條,左連接有時候也需要給右表中的字段加索引,右連接的同時也需要給左表的某些字段加索引,這些需要根據業務靈活變動,尤其6、7兩條。

 

二、索引失效原則

有時候以爲SQL語句的書寫不當會導致索引的失效,就比如對於符合索引中,如果其中一個失效了,那麼它後面的幾個索引也會失效。例如符合索引(a,b,c),如果a失效了那麼b,c也會失效,b失效了,c就會失效。下面將幾條避免索引失效的原則:

    1.複合索引,不要跨列或無序使用,否則索引失效。這個和上面講優化手段的第1條和第3.3條一致。

    2.複合索引儘量使用全索引匹配,建幾個用幾個。比如建了(a,b)那麼a,b字段最好都用上;建了(a,b,c)那麼abc三個字段最好都用上

   3.不要在索引上進行任何操作(+、-、*、/等計算、函數、類型轉換等),否則索引失效

        計算:比如id是索引字段,那麼如下SQL就會使索引失效:select ...where a.id*3

        函數:比如id是索引字段,類似這樣的SQL也會讓索引失效:select count(id) from....

        類型轉換:比如給那麼字段設置了索引,並且那麼字段的類型是char,那麼下面第二句的SQL就會導致索引失效:

                          select * from teacher where name='abc'  //索引不失效

                          select * from teacher where name=123   //索引失效

    4.複合索引不要使用不等於(!=  <>)或 is null(is not null)否則自身及右側所有索引全部失效 ,比如select...from A where id !=2...

    5.like儘量以“常量”開頭,不要以“%”開頭,否則索引失效,比如select * from xx where name like '%X%';如果name字段是索引,則會失效。

    6.儘量不要使用or,否則索引失效。比如select * from teacher where name='abc' or id=3;id和name都會失效。

 

上面說了那麼多手段和原則,但有一點必須得說明一下,其實SQL優化是一種概率事件,也就是說你優化了不一定會百分之百的有效,主要原因就是MySQL的服務層中SQL優化器可能會對你優化後的SQL語句進行進一步優化更改,從而導致原有的優化失效。

但我們該優化還是需要優化,哈哈。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章