慢sql優化(1):limit下desc和asc性能相差百倍 一次慢查詢的優化 V1升級(分批次查詢) V2升級(limit底層原理) 相關文章

背景:Task任務,需要將最近一天的數據查詢出來,然後同步到kafka中。

一次慢查詢的優化

環境:mysql5.7.26
表數據總量:4千萬數據
單日數據量:1百萬數據
表索引:date_user_id_idx(date、user_id)
主鍵索引:id
需求:需要將60天的數據歸到到歷史表,需要將昨天的數據同步給kafka。
sql:select * from tb_temp where date = 20220919

V1升級(分批次查詢)

優化方案:限制每次查詢的數量,分多次查出。

此時我們一般會使用分頁查詢。

  • 一般最常用的就是offset+limit偏移量進行查詢。但是會帶來深分頁的性能損耗,是不推薦使用這種方案的。

  • 另一種方案就是使用cursor遊標的方式,每次查詢數據後得到一個遊標id,然後代入到sql條件中。

select * from  tb_temp where date = 20220919 and id<=xxx order by id desc limit 5000

思考點:爲什麼要專門引入order by id desc

需要注意的時候,因爲select * from tb_temp where date = 20220919命中的是date_user_id_idx索引。聯合索引的規則:先根據date排序、然後根據user_id排序。所以這條只查詢date返回的主鍵id是無序的。而遊標分頁需要一個有序的遊標id,我們既然要藉助主鍵id作爲遊標id,所以需要專門引入order by id desc

思考點:爲什麼引入order by id desc後,sql執行計劃從date_user_id_idx變爲主鍵索引

如果命中date_user_id_idx二級索引的話,我們得到的二級索引下的id是無序的。那麼會帶來回表查詢+文件排序(100w數據)的性能損耗。那麼在mysql看來直接走id主鍵索引性能會更優。

V2升級(limit底層原理)

而我們採用的正是V1版本的升級方案(遊標法),但代碼執行過程依舊會存在兩種慢sql。

慢sql-1(歸檔歷史數據):select * from tb_temp where date = 20220723 and id<=9223372036854775807(因爲第一次的的時候給框架默認遊標是Long.MAX) order by id desc limit 5000

慢sql-2(同步昨日數據):select * from tb_temp where date = 20220921 and id<=12344566(“臨界數據的id”) order by id desc limit 5000

limit 200執行原理:

  1. 在表中拉取到200條數據後,終止;
  2. 掃描完where條件規定的返回後,終止;

當查詢出的行數無法滿足limit的限制時,mysql需要將where範圍內的數據全部掃描完,流程纔會被結束。

那麼我們可以看到:

慢sql1(歸檔歷史數據):由於是第一次查詢所以框架給的遊標id是默認值也就是Long的最大值。而0723的數據分佈在聚簇索引樹最左側,本次查詢就會在聚簇索引樹的最右側發起查詢。期間會涉及到大量的IO操作,導致我們查詢很慢。但是(歸檔歷史數據)的sql第二次查詢的時候:select * from tb_temp where date = 20220723 and id<=111122002 order by id desc limit 5000已經確定了範圍,就會導致查詢非常快速。

慢sql1(同步昨日數據):20220921數據分佈在聚簇索引樹的最右側,所以開始的時候查詢效率很高,但是到了20220921數據的邊界處時,只查詢到了288條數據,沒有滿足5000條數據的終止條件,且我們並沒有給出終止條件(date不是終止條件,而是篩選條件)所以依舊會一路去左查詢滿足條件的數據,直到遍歷完全表數據。

優化方案:

mysql的id是自增主鍵,邏輯上我們認爲date和id是有關係的。所以需要查詢當天的第一筆或者最後一筆id,作爲終止limit的條件。

以同步昨日數據爲例:

-- 先查詢上一天的最後一筆數據的lastId
select id from tb_temp where date = 20220920 order by id desc limit 1;

- 再將這筆id代入到下面sql中
elect * from tb_temp where date = 20220921 and id<=12344566 and id>lastId order by id desc limit 5000

彩蛋:

爲什麼要查詢上一天最後一筆數據的id,而不是當天的第一筆id,其實也和上面講的有關係。

如果查詢20220921的第一筆id的話(select id from tb_temp where date = 20220921 order by id limit 1)那麼mysql會從聚簇索引樹的最左側開始尋找(而實際上這筆數據位於最右側,從而帶來很多無用的性能開銷)。

相關文章

MySQL正序和倒序排序思考

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