在线不卡日本ⅴ一区v二区_精品一区二区中文字幕_天堂v在线视频_亚洲五月天婷婷中文网站

  • <menu id="lky3g"></menu>
  • <style id="lky3g"></style>
    <pre id="lky3g"><tt id="lky3g"></tt></pre>

    MySQL 查詢語句的 limit, offset 是怎么實現(xiàn)的?

    在寫 select 語句的時候,使用 limit, offset 可能就像是我們吃飯喝水一樣自然了。

    剛開始工作的時候也經(jīng)常聽前輩們教導:使用 limit, offset,當 offset 變大的時候執(zhí)行效率會越來越低。

    相信在前輩們的言傳身教,和自己的實戰(zhàn)過程中,大家也都知道了為什么會這樣。

    因為 select 在執(zhí)行過程中,對于存儲引擎返回的記錄,經(jīng)過 server 層的 WHERE 條件篩選之后,符合條件的前 offset條記錄,會被直接無情的拋棄,直到符合條件的第 offset + 1 條記錄,才開始發(fā)送給客戶端,發(fā)送了 limit 條記錄之后,查詢結(jié)束。

    雖然知道了是什么,也知道了為什么,但是我也一直好奇底層是怎么實現(xiàn)的,所以今天我們來扒一扒它的廬山真面目。

    1. 語法回顧

    先來簡單的回顧一下 select 語句中 limit, offset 的語法,MySQL 支持 3 種形式:

    • LIMIT limit: 因為沒有指定 offset,所以 offset = 0,表示讀取符合 WHERE 條件的第 1 ~ limit 條記錄。
    • LIMIT offset, limit: 我們常用的就是這種了。
    • LIMIT limit OFFSET offset: 這種不常用。

    offset 和 limit 的值都不能為負數(shù),在源碼里這兩個屬性定義的是無符號整數(shù),并且在解析階段就做了限制,如果為負數(shù),直接報語法錯誤了。

    2. 語法解析階段

    在讀取數(shù)據(jù)的過程中,對于符合條件的前 offset 條記錄,會直接忽略,不發(fā)送給客戶端,從符合條件的第 offset + 1 條記錄開始,發(fā)送 limit 條記錄給客戶端。

    所以,server 層實際上需要從存儲引擎讀取 offset + limit 條記錄,源碼里也是這么實現(xiàn)的,語法解析階段,在驗證了 offset 和 limit 都是大于等于 0 的整數(shù)之后,就把 offset + limit 的計算結(jié)果保存到一個叫做 select_limit_cnt 的屬性里,offset 也會保存到一個叫做 offset_limit_cnt 的屬性里。

    3. 發(fā)送數(shù)據(jù)階段

    來到發(fā)送數(shù)據(jù)階段,此時的記錄已經(jīng)通過了 WHERE 條件的篩選,接下來就是判斷這條記錄是不是要發(fā)送給客戶端。

    第 1 步

    因為 offset 已經(jīng)保存到 offset_limit_cnt 中了,先來判斷 offset_limit_cnt 是否大于 0,如果大于 0,這條記錄就會被拋棄了,不發(fā)送給客戶端;如果等于 0,記錄就具備了發(fā)送給客戶端的資格了,然后接著進入第 2 步。

    在拋棄記錄之前,還會干一件事:對一個叫做 send_records 的屬性進行加 1 操作,就是假裝這條記錄已經(jīng)發(fā)送了(為什么這樣干?第 2 步會用到這個屬性)。

    offset_limit_cnt 是保證不會小于 0 的,所以在這一步只需要判斷是大于 0 還是等于 0 就可以了。

    第 2 步

    來到這一步,記錄就具備了發(fā)送給客戶端的資格了,至于要不要發(fā),就看客戶端想不想要它了,而客戶端想不想要它,取決于 select_limit_cnt。

    所以,在這一步要判斷已發(fā)送記錄數(shù)量(send_records)和需要發(fā)送記的錄數(shù)量(select_limit_cnt)之間的關(guān)系,如果已發(fā)送記錄數(shù)量大于等于需要發(fā)送的記錄數(shù)量,則結(jié)束查詢,否則就接著進入第 3 步。

    第 3 步

    在這里,記錄愉快的等待著被發(fā)送給客戶端。

    是的,還要愉快的等著,因為要排隊,畢竟運輸也是需要成本的,不能來一條記錄,就發(fā)一趟車,要等一輛車裝滿之后,才會發(fā)車的。這里的車指的是網(wǎng)絡(luò)緩沖區(qū),以后也會寫文章介紹,敬請期待。

    4. 最佳實踐

    既然在 offset 變大之后,使用 limit, offset 效率越來越低,那應該怎么辦呢?對于實戰(zhàn)經(jīng)驗豐富的小伙伴來說,這是相當簡單了,但是以防萬一剛看到本文的小伙伴是剛剛開始用 SQL 寫 Bug,所以還是要大概的寫一下的。

    以一個 SQL 為例:

    select * from t2where i1 > 90000000 limit 8888, 10

    為了取到 10 條記錄,要先找到 8888 條記錄,然后取到需要的 10 條,前面 8888 條記錄都白找了,太浪費了,可以這樣修改一下:

    select * from t2 where i1 > 90000000 and id > LAST_MAX_IDlimit 10

    LAST_MAX_ID 是上一次執(zhí)行 SQL 時讀取到的主鍵 ID 的最大值,如果是第一次執(zhí)行語句,LAST_MAX_ID = 0。

    不過這種方案也有個問題,不支持跳著翻頁,只支持順序翻頁(就是每次都點下一頁的這種)。

    如果要支持跳著翻頁,怎么辦?

    只用 MySQL 這把錘子顯然有點不夠用了,還要再找一把錘子(Redis),可以把符合條件的記錄的主鍵 ID 都讀取出來,存入到 Redis 的有序集合(zset)中,用 zset 相應的函數(shù)讀取到某一頁應該展示的數(shù)據(jù)對應的那些主鍵 ID,然后用這些主鍵 ID 去 MySQL 中查詢對應的數(shù)據(jù),從而用兩把錘子間接的實現(xiàn)了分頁功能。

    當然,這個方案也是有適用場景的,比如,這個方案明顯就不適用于這些場景:符合條件的記錄非常非常多導致存主鍵 ID 到 Redis 要占用很大的內(nèi)存、記錄更新頻繁導致存主鍵 ID 的緩存經(jīng)常被清除。如果碰到更復雜的場景,就要結(jié)合業(yè)務(wù)具體情況具體分析了

    以上就是本文的全部內(nèi)容了,如果本文對你有所幫助,還請幫忙 轉(zhuǎn)發(fā)、點贊,謝謝 ^_^

    鄭重聲明:本文內(nèi)容及圖片均整理自互聯(lián)網(wǎng),不代表本站立場,版權(quán)歸原作者所有,如有侵權(quán)請聯(lián)系管理員(admin#wlmqw.com)刪除。
    上一篇 2022年6月20日 06:34
    下一篇 2022年6月20日 06:35

    相關(guān)推薦

    聯(lián)系我們

    聯(lián)系郵箱:admin#wlmqw.com
    工作時間:周一至周五,10:30-18:30,節(jié)假日休息