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

          分頁場景慢?MySQL的鍋!

          共 2756字,需瀏覽 6分鐘

           ·

          2021-05-24 17:49


          從一個(gè)問題說起




          在剛工作的時(shí)候,發(fā)現(xiàn)分頁場景下,當(dāng)offset變大,MySQL處理速度非常慢!具體sql如下:

          select * from t_record where age > 10 offset 10000 limit 10


          下表所示為表t_record結(jié)構(gòu),為了簡單起見,只列了我們將討論的字段,其余字段省略。

          字段名
          類型
          描述
          id
          bigint(20) unsigned
          主鍵id
          age
          int
          年齡

          其中t_record是要查詢的數(shù)據(jù)表,表中一共有50000條記錄,age字段上有索引,且age>10的記錄有20000條。

          這條語句非常慢,基本達(dá)到了秒級(jí)延遲,在第二次請(qǐng)求有緩存之后,才變快。

          在數(shù)據(jù)量這么少的情況下,走索引還這么慢,這完全不能接受,我就問我導(dǎo)師為什么,他反問“索引場景,MySQL中獲得第n大的數(shù),時(shí)間復(fù)雜度是多少?

          答案的追尋



          小白直覺作答


          當(dāng)時(shí)只知道MySQL索引使用的是樹,瞎猜了個(gè)O(logn),心想二叉樹找一個(gè)節(jié)點(diǎn)不就是O(logn)么。自然而然,導(dǎo)師白了一眼,讓我自己去研究。

          繼續(xù)解答


          想來想去...只能從底層結(jié)構(gòu)分析了,MySQL的索引是B+樹。仔細(xì)想一下,就會(huì)發(fā)現(xiàn)通過索引去找很別扭。因?yàn)槟悴恢狼皀個(gè)數(shù)在其他子樹的分布情況,也沒有標(biāo)記讓你能快速選擇去哪個(gè)子樹尋找,我們無法利用B+樹分支過濾的查找特性。

          這下我明白導(dǎo)師的用意了——offset n,就是從第n大的數(shù)開始找!第n大的數(shù)沒法使用樹分支查找,所以offset,也不能!

          回到我們一開始的問題:
          select * from t_record where age > 10 offset 10000 limit 10

          通過二級(jí)索引age,我們只能找到對(duì)應(yīng)的起始節(jié)點(diǎn),但無法通過樹結(jié)構(gòu)過濾掉10000個(gè)節(jié)點(diǎn),再獲取10個(gè)節(jié)點(diǎn),因?yàn)槲覀儫o法知道某個(gè)子樹下有多少數(shù)據(jù),就無法通過分支進(jìn)行排除。

          那該怎么辦呢?

          我們來仔細(xì)看下B+樹的結(jié)構(gòu),它不光有常規(guī)樹的分支結(jié)構(gòu),底部還有一個(gè)由葉子節(jié)點(diǎn)組成鏈表。

          顯而易見,最方便最快的方式,就是用樹定位到起始位置,然后直接通過葉子節(jié)點(diǎn)組成的鏈表,以O(shè)(n)的復(fù)雜度找到第n大的數(shù)據(jù)。

          回到我們最初的問題,總結(jié)一下:問題的本質(zhì)其實(shí)就是讓offset找到第n大的數(shù),再通過鏈表遍歷,在數(shù)據(jù)量很大的情況下,確實(shí)會(huì)慢。

          但是即使是O(n),也不至于僅有幾萬數(shù)據(jù)就慢得令人發(fā)指。

          是不是還有其他影響因素?

          系統(tǒng)學(xué)習(xí)

          牛牛決定深入研究,帶著問題去查找了很多資料。

          這里推薦兩本書,一本《MySQL技術(shù)內(nèi)幕 InnoDB存儲(chǔ)引擎》,通過它可以對(duì)InnoDB的底層機(jī)制,如acid、mvcc、索引實(shí)現(xiàn)、文件存儲(chǔ),有更深的理解。

          第二本是《高性能MySQL》,這本書從使用層面著手,講得比較深入,并提到了很多設(shè)計(jì)和優(yōu)化的思路,對(duì)日常工作和學(xué)習(xí)都有很大的幫助。

          兩本書相結(jié)合,反復(fù)領(lǐng)會(huì),MySQL就差不多能登堂入室了。

          針對(duì)我們的問題,這里介紹兩個(gè)相關(guān)的概念:

          • 聚簇索引:包含主鍵索引和對(duì)應(yīng)的實(shí)際數(shù)據(jù),索引的葉子節(jié)點(diǎn)就是數(shù)據(jù)節(jié)點(diǎn);


          • 輔助索引:也叫二級(jí)節(jié)點(diǎn),其葉子節(jié)點(diǎn)還是索引節(jié)點(diǎn),并沒有完整的數(shù)據(jù),僅包含了索引值本身和主鍵id,用主鍵id反查聚蔟索引才能獲取完整數(shù)據(jù)。



          如圖所示,offset會(huì)先從二級(jí)索引的鏈表順序找10000個(gè)節(jié)點(diǎn)。


          注意,即使這10000個(gè)節(jié)點(diǎn)會(huì)被扔掉,MySQL也會(huì)通過二級(jí)索引上的主鍵id,去聚簇索引上查一遍數(shù)據(jù),這可是10000次隨機(jī)IO,自然慢成哈士奇。

          大家讀到這里可能會(huì)提出疑問,為什么MySQL會(huì)有這種行為?


          這和它的優(yōu)化器有關(guān)系,也算是MySQL的一個(gè)大坑,時(shí)至今日,也沒有優(yōu)化。

          問題的解決



          針對(duì)分頁性能問題,《高性能MySQL》中提到了兩種方案,讓我們一起來看看:

          方案一:產(chǎn)品上繞過


          根據(jù)業(yè)務(wù)實(shí)際需求,看能否替換為上一頁、下一頁的功能,這樣子就可以通過和上次返回?cái)?shù)據(jù)進(jìn)行比較,搭上樹分支過濾的便車。


          特別在ios,android端,以前那種完全的分頁是不常見的。即轉(zhuǎn)換為如下sql,第一次last_id傳0即可。

          select * from t_record where id > last_id  limit 10

          優(yōu)點(diǎn)

          1.能利用樹的分支結(jié)構(gòu),過濾掉第n個(gè)數(shù)之前的數(shù)據(jù)集;

          2.直接通過主鍵索引查找,省略了二級(jí)索引查找過程,性能會(huì)更高。

          缺點(diǎn)

          1.使用場景其實(shí)是受限制的。比如,如果是針對(duì)age字段有條件判斷,再分頁,那么使用主鍵id查找就不滿足需求;

          2.把主鍵id暴露出去了,這個(gè)本身不應(yīng)該是業(yè)務(wù)層面關(guān)心的字段。

          可以看到,該方案在我們的場景中,是不適用的。


          因?yàn)槲覀冞€有age做過濾條件,此時(shí)用大于主鍵id的方式,雖然看起來變成順序IO了,但由于是根據(jù)主鍵id排列來尋找,而不是根據(jù)需要的age索引,所以會(huì)導(dǎo)致MySQL去查更多的數(shù)據(jù)。雖然不符合我們案例的需求,但還是來看看優(yōu)缺點(diǎn):


          方案二:正面剛


          這里先介紹一個(gè)概念:

          索引覆蓋:當(dāng)輔助索引查詢的數(shù)據(jù)只有主鍵id和輔助索引本身,那么就不必再去查聚簇索引。

          思路如下:

          select * from t_record id in(select id from t_record where age > 10 offset 10000 limit 10

          這句話是說,先從條件查詢中,查找數(shù)據(jù)對(duì)應(yīng)的數(shù)據(jù)庫唯一id值,因?yàn)橹麈I在輔助索引上就有,所以不用回歸到聚簇索引的磁盤上拉取。

          如此以來,offset部分均不需要去反查聚蔟索引,只有l(wèi)imit出來的10個(gè)主鍵id會(huì)去查詢聚簇索引,這樣只會(huì)十次隨機(jī)IO。

          在業(yè)務(wù)確實(shí)需要用分頁的情況下,使用該方案可以大幅度提高性能。通常能滿足性能要求。


          優(yōu)點(diǎn)

          1.維持了分頁需求,適用所有l(wèi)imit offset場景,大大減少隨機(jī)IO,提高了性能;

          2.二級(jí)索引上,只查找id,傳輸?shù)臄?shù)據(jù)包也變小。

          缺點(diǎn)

          二級(jí)索引上還是會(huì)走下面的鏈表來遍歷,這部分時(shí)間復(fù)雜度還是O(n)。


          方案選型


          如果產(chǎn)品本身的需求,是分上下頁,且沒用其他過濾條件,可以用方案一。

          方案二更具有普適性,同時(shí)由于合理分表的大小,一般也就500w,二級(jí)索引上O(n)的查找損耗,通常也在可接受范圍。

          總結(jié)



          從一個(gè)小問題,往下深究,不僅可以深入理解這個(gè)問題,在面試和工作中大放異彩,同時(shí)在探索的過程中,自身的知識(shí)儲(chǔ)備也能得到拓展,是技術(shù)的一個(gè)提升捷徑。


          瀏覽 62
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  亚洲激情在线观看 | 黄片视频在线看 | 成人麻豆精品 | 一级片黄色片视频 | 久久久久无码国产精品不卡 |