sql查詢調優之where條件排序字段以及limit使用索引的奧祕

奇怪的慢sqlmysql

咱們先來看2條sqlsql

第一條:數據庫

select * from acct_trans_log WHERE  acct_id = 1000000000009000757 order by create_time desc limit 0,10學習

  

第二條:大數據

 select * from acct_trans_log WHERE  acct_id = 1000000000009003061 order by create_time desc limit 0,103d

表的索引及數據總狀況:指針

 

索引:acct_id,create_time分別是單列索引,數據庫總數據爲500wblog

經過acct_id過濾出來的結果集在1w條左右排序

 

查詢結果:第一條要5.018s,第二條0.016s索引

爲何會是這樣的結果呢?第一,acct_id和create_time都有索引,不該該出現5s查詢時間這麼慢啊

 

那麼先來看執行計劃

第一條sql執行計劃:

 第二條執行計劃:

 仔細觀察會發現,索引只使用了idx_create_time,沒有用到idx_acct_id

這能解釋第一條sql很慢,由於where查詢未用到索引,那麼第二條爲何這麼快?

看起來匪夷所思,其實搞清楚mysql查詢的原理以後,其實很簡單

 

咱們來看這2條sql查詢,都用到了where order by limit

當有limit存在時,查詢的順序就有可能發生變化,這時並非從數據庫中先經過where過濾再排序再limit

由於若是這樣的話,從500萬數據中經過where過濾就不會是5s了。

 

 

此時的執行順序是,先根據idx_create_time索引樹,從最右側葉子節點,反序取出n條,而後逐條去跟where條件匹配

若匹配上,則得出一條數據,直至取滿10條爲止,爲何第二條sql要快,由於運氣好,恰好時間倒序的前幾條就所有知足了。

 

搞清楚原理以後,咱們瞭解了爲何第一條慢,第二條快的緣由,可是問題又來了

爲何mysql不用idx_acct_id索引,這是一個問題,由於這樣的話,咱們的創建的索引基本失效了,在此類sql下

查詢效率將會是至關低

 

由於經過acct_id過濾出來的結果集比較大,有上萬條,mysql認爲按時間排序若是不用索引,將會是filesort,這樣會很慢,而又不能2個索引都用上

,因此選擇了idx_create_time。

 

爲何mysql只用一個索引

這裏爲何不能2個索引都用上,可能不少人也不知道爲何,其實道理很簡單,每一個索引在數據庫中都是一個索引樹,其數據節點存儲了指向實際

數據的指針,若是用一個索引來查詢,其原理就是從索引樹上去檢索,並得到這些指針,而後去取出數據,試想,若是你經過一個索引,獲得過濾後的指針,這時,你的另外一個條件索引若是再過濾一遍,將獲得2組指針的集合,若是這時候取交集,未必就很快,由於若是每一個集合都很大的話,取交集的時候,等於掃描2個集合,效率會很低,因此無法用2個索引。固然有時候mysql會考慮臨時創建一個聯合索引,將2個索引聯合起來用,可是並非每種狀況都能奏效,一樣的道理,用一個索引檢索出結果集以後,排序時,也沒法用上另外一個索引了。

 

實際上用索引idx_acct_id大多數狀況仍是要比用索引idx_create_time要快,咱們舉個例子:

select * from acct_trans_log force index(idx_acct_id) WHERE  acct_id = 1000000000009000757 order by create_time desc limit 0,10

耗時:0.057s

能夠看出改狀況用idx_acct_id索引是比較快的,那麼是否是這樣就能夠了呢,排序未用上索引,始終是有隱患的。

 

 

聯合索引讓where和排序字段同時用上索引

咱們來看下一條sql:

select * from acct_trans_log force index(idx_acct_id) WHERE  acct_id = 3095  order by create_time desc limit 0,10

耗時: 1.999s

執行計劃:

 該sql經過acct_id過濾出來的結果集有100萬條,所以排序將會耗時較高,所幸這裏只是取出前10條最大的而後排序

查詢概況,咱們發現時間基本消耗在排序上,其實這是內存排序,對內存消耗是很高的。

 

 那麼咱們有沒有其它解決方案呢,這種sql是咱們最多見的,若是處理很差,在大數據量的狀況下,耗時以及對數據庫資源的消耗都很高

,這是咱們所不能接受的,咱們的惟一解決方案就是讓where條件和排序字段都用上索引

 

解決辦法就是創建聯合索引:

alter table acct_trans_log add index idx_acct_id_create_time(acct_id,create_time)

而後執行sql:

select * from acct_trans_log WHERE  acct_id = 3095  order by create_time desc limit 0,10

耗時: 0.016s

 聯合索引讓where條件字段和排序字段都用上了索引,問題解決了!

 

聯合索引使用的原理

可是爲何能解決這個問題呢,這時你們可能就會記住一個死理,就是聯合索引能夠解決where過濾和排序的問題,也不去了解

其原理,這樣是不對的,由於當狀況發生變化,就懵逼了,下面咱們再看一個sql:

select * from acct_trans_log force index(idx_acct_id_create_time) WHERE  acct_id in(3095,1000000000009000757)  order by create_time desc limit 0,10

耗時:1.391s

索引仍是用idx_acct_id_create_time,時間竟然慢下來了

執行計劃是:

 

 

  看執行計劃,排序用到了filesort,也就是說,排序未用到索引。

 

那麼咱們仍是來看看,索引排序的原理,咱們先來看一個sql:

select * from acct_trans_log ORDER BY create_time limit 0,100

耗時:0.029s

執行計劃爲:

 這裏執行的步驟是,先從索引樹中,按時間升序取出前100條,由於索引是排好序的,直接左序遍歷便可了

所以,這裏mysql並無作排序動做,若是想降序,則右序遍歷索引樹,取出100條便可,查詢當然快,

 

那麼聯合索引的時候,是怎樣的呢?

select * from acct_trans_log WHERE  acct_id = 3095  order by create_time desc limit 0,10

使用組合索引:idx_acct_id_create_time

這個時候,由於acct_id是聯合索引的前綴,所以能夠很快實行檢索,

若是sql是

select * from acct_trans_log WHERE  acct_id = 3095

出來的數據是按以下邏輯排序的

3095+time1

3095+time2

3095+time3

默認是升序的,也就是說,次sql至關於

select * from acct_trans_log WHERE  acct_id = 3095 order by create_time

他們是等效的。

若是咱們把條件換成order by create_time desc limit 0,10呢

這時候,應該從idx_acct_id_create_time樹右邊葉子節點倒序遍歷,取出前10條便可

由於數據的前綴都是3095,後綴是時間升序。那麼咱們倒序遍歷出的數據,恰好知足order by create_time desc

所以也無需排序。

 

那麼語句:

select * from acct_trans_log force index(idx_acct_id_create_time) WHERE  acct_id in(3095,1000000000009000757)  order by create_time desc limit 0,10

爲何排序沒法用索引呢?

咱們先分析下索引的排序規則

已知:id1<id2<id3...  time1<time2<time3....

查詢結果集排序以下:

id1+time1

id1+time2

id1+time3

id2+time1

id2+time2

id2+time3

 

索引出來的默認排序是這樣的,id是有序的,時間是無序的,由於有2個id,優先按id排序,時間就是亂的了,

這樣排序將會用filesort,這就是慢的緣由,也是排序沒有用到索引的緣由。

  

查詢計劃使用以及使用說明

table:顯示這一行數據是關於哪張表的

type:顯示使用了何種類型,從最好到最差的鏈接類型爲const,eq_ref,ref,range,index,all

possible_keys:顯示可能應用在這張表中的索引。若是爲空,沒有可能的索引

key:實際使用的索引,若是爲null,則沒有使用索引。

key_len:使用的索引的長度。在不損失精確性的狀況下,長度越短越好

ref:顯示索引的哪一列被使用了,若是可能的話,是一個常數

rows:mysql認爲必須檢查的用來返回請求數據的行數

做者:風過無痕-唐
出處:http://www.cnblogs.com/tangyanbo/ 本文以學習、研究和分享爲主,歡迎轉載,但必須在文章頁面明顯位置給出原文鏈接。 若是文中有不妥或者錯誤的地方還望高手的你指出,以避免誤人子弟。若是以爲本文對你有所幫助不如【推薦】一下!若是你有更好的建議,不如留言一塊兒討論,共同進步! 再次感謝您耐心的讀完本篇文章。歡迎加QQ討論羣

相關文章
相關標籤/搜索