<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          SQL 查詢優(yōu)化之 WHERE 和 LIMIT 使用索引的奧秘

          共 3613字,需瀏覽 8分鐘

           ·

          2021-01-30 10:25

          點擊關(guān)注上方“SQL數(shù)據(jù)庫開發(fā)”,

          設(shè)為“置頂或星標”,第一時間送達干貨

          作者:風過無痕-唐

          www.cnblogs.com/tangyanbo/p/6378741.html

          奇怪的慢sql


          我們先來看2條sql


          第一條:


          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,10



          表的索引及數(shù)據(jù)總情況:
          ?
          索引:acct_id,create_time 分別是單列索引,數(shù)據(jù)庫總數(shù)據(jù)為500w。

          通過 acct_id 過濾出來的結(jié)果集在 1w 條左右。
          ?
          查詢結(jié)果:第一條要5.018s,第二條0.016s

          為什么會是這樣的結(jié)果呢?第一,acct_id和create_time都有索引,不應(yīng)該出現(xiàn)5s查詢時間這么慢啊
          ?
          那么先來看執(zhí)行計劃

          第一條sql執(zhí)行計劃:

          ?第二條執(zhí)行計劃:

          ?仔細觀察會發(fā)現(xiàn),索引只使用了idx_create_time,沒有用到idx_acct_id。

          這能解釋第一條sql很慢,因為where查詢未用到索引,那么第二條為什么這么快?
          看起來匪夷所思,其實搞清楚mysql查詢的原理之后,其實很簡單。
          ?
          我們來看這2條sql查詢,都用到了where order by limit。

          當有l(wèi)imit存在時,查詢的順序就有可能發(fā)生變化,這時并不是從數(shù)據(jù)庫中先通過where過濾再排序再limit。

          因為如果這樣的話,從500萬數(shù)據(jù)中通過where過濾就不會是5s了。
          ?
          此時的執(zhí)行順序是,先根據(jù)idx_create_time索引樹,從最右側(cè)葉子節(jié)點,反序取出n條,然后逐條去跟where條件匹配。

          若匹配上,則得出一條數(shù)據(jù),直至取滿10條為止,為什么第二條sql要快,因為運氣好,剛好時間倒序的前幾條就全部滿足了。
          ?
          搞清楚原理之后,我們了解了為什么第一條慢,第二條快的原因,但是問題又來了
          為什么mysql不用idx_acct_id索引,這是一個問題,因為這樣的話,我們的建立的索引基本失效了,在此類sql下查詢效率將會是相當?shù)汀?/span>
          ?
          因為通過acct_id過濾出來的結(jié)果集比較大,有上萬條,mysql認為按時間排序如果不用索引,將會是filesort,這樣會很慢,而又不能2個索引都用上,所以選擇了idx_create_time。
          ?
          為什么mysql只用一個索引

          這里為什么不能2個索引都用上,可能很多人也不知道為什么,其實道理很簡單,每個索引在數(shù)據(jù)庫中都是一個索引樹,其數(shù)據(jù)節(jié)點存儲了指向?qū)嶋H數(shù)據(jù)的指針,如果用一個索引來查詢,其原理就是從索引樹上去檢索,并獲得這些指針,然后去取出數(shù)據(jù)。

          試想,如果你通過一個索引,得到過濾后的指針,這時,你的另一個條件索引如果再過濾一遍,將得到2組指針的集合,如果這時候取交集,未必就很快,因為如果每個集合都很大的話,取交集的時候,等于掃描2個集合,效率會很低,所以沒法用2個索引。

          當然有時候mysql會考慮臨時建立一個聯(lián)合索引,將2個索引聯(lián)合起來用,但是并不是每種情況都能奏效,同樣的道理,用一個索引檢索出結(jié)果集之后,排序時,也無法用上另一個索引了。
          ?
          實際上用索引idx_acct_id大多數(shù)情況還是要比用索引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索引是比較快的,那么是不是這樣就可以了呢,排序未用上索引,始終是有隱患的。
          ?
          聯(lián)合索引讓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

          執(zhí)行計劃:

          ?該sql通過acct_id過濾出來的結(jié)果集有100萬條,因此排序?qū)臅r較高,所幸這里只是取出前10條最大的然后排序

          查詢概況,我們發(fā)現(xiàn)時間基本消耗在排序上,其實這是內(nèi)存排序,對內(nèi)存消耗是很高的。
          ?

          那么我們有沒有其它解決方案呢,這種sql是我們最常見的,如果處理不好,在大數(shù)據(jù)量的情況下,耗時以及對數(shù)據(jù)庫資源的消耗都很高,這是我們所不能接受的,我們的唯一解決方案就是讓where條件和排序字段都用上索引
          ?
          解決辦法就是建立聯(lián)合索引:



          alter?table?acct_trans_log add?index?idx_acct_id_create_time(acct_id,create_time)



          然后執(zhí)行sql:


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


          耗時: 0.016s



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

          ?

          聯(lián)合索引使用的原理


          但是為什么能解決這個問題呢,這時大家可能就會記住一個死理,就是聯(lián)合索引可以解決where過濾和排序的問題,也不去了解其原理,這樣是不對的,因為當情況發(fā)生變化,就懵逼了,


          下面我們再看一個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,時間居然慢下來了。

          執(zhí)行計劃是:
          ?
          ?
          ?看執(zhí)行計劃,排序用到了filesort,也就是說,排序未用到索引。
          ?
          那么我們還是來看看,索引排序的原理,我們先來看一個sql:



          select?* from?acct_trans_log
          ORDER?BY?create_time limit?0,100


          耗時:0.029s

          執(zhí)行計劃為:

          ?這里執(zhí)行的步驟是,先從索引樹中,按時間升序取出前100條,因為索引是排好序的,直接左序遍歷即可了,因此,這里mysql并沒有做排序動作,如果想降序,則右序遍歷索引樹,取出100條即可,查詢固然快,那么聯(lián)合索引的時候,是怎樣的呢?



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


          使用組合索引:idx_acct_id_create_time。
          這個時候,因為acct_id是聯(lián)合索引的前綴,因此可以很快實行檢索,如果sql是



          select?* from?acct_trans_log WHERE??acct_id = 3095


          出來的數(shù)據(jù)是按如下邏輯排序的
          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呢?

          這時候,應(yīng)該從idx_acct_id_create_time樹右邊葉子節(jié)點倒序遍歷,取出前10條即可

          因為數(shù)據(jù)的前綴都是3095,后綴是時間升序。那么我們倒序遍歷出的數(shù)據(jù),剛好滿足 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


          為什么排序無法用索引呢?

          我們先分析下索引的排序規(guī)則,
          已知:id1

          查詢結(jié)果集排序如下:
          id1+time1
          id1+time2
          id1+time3
          id2+time1
          id2+time2
          id2+time3
          ?
          索引出來的默認排序是這樣的,id是有序的,時間是無序的,因為有2個id,優(yōu)先按id排序,時間就是亂的了,這樣排序?qū)胒ilesort,這就是慢的原因,也是排序沒有用到索引的原因。

          ??

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


          table:顯示這一行數(shù)據(jù)是關(guān)于哪張表的
          type:顯示使用了何種類型,從最好到最差的連接類型為const,eq_ref,ref,range,index,all
          possible_keys:顯示可能應(yīng)用在這張表中的索引。如果為空,沒有可能的索引
          key:實際使用的索引,如果為null,則沒有使用索引。
          key_len:使用的索引的長度。在不損失精確性的情況下,長度越短越好
          ref:顯示索引的哪一列被使用了,如果可能的話,是一個常數(shù)
          rows:mysql認為必須檢查的用來返回請求數(shù)據(jù)的行數(shù)

          我是岳哥,最后給大家分享我寫的SQL兩件套:《SQL基礎(chǔ)知識第二版》《SQL高級知識第二版》的PDF電子版。里面有各個語法的解釋、大量的實例講解和批注等等,非常通俗易懂,方便大家跟著一起來實操。


          有需要的讀者可以下載學習,在下面的公眾號「數(shù)據(jù)前線」(非本號)后臺回復(fù)關(guān)鍵字:SQL,就行

          數(shù)據(jù)前線
          ——End——

          后臺回復(fù)關(guān)鍵字:1024,獲取一份精心整理的技術(shù)干貨

          后臺回復(fù)關(guān)鍵字:進群,帶你進入高手如云的交流群。

          推薦閱讀

          瀏覽 89
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美乱伦一区 | 91啦丨国产丨人妻白洁 | 欧美性青草视频在线看 | 黄色搞逼视频 | 亚洲日韩在线电影 |