MySQL大數據量分頁limit優化

一、limit用法

SELECT * FROM t LIMIT 10,10;

  • 第一個參數指定第一個返回記錄行的偏移量
  • 第二個參數指定返回記錄行的最大數目
  • 如果只給定一個參數:它表示返回最大的記錄行數目
  • 第二個參數爲 -1 表示檢索從某一個偏移量到記錄集的結束所有的記錄行
  • 初始記錄行的偏移量是 0(而不是 1)

所以上面SQL的含義是查詢數據庫第10條到第20條數據

對於小的偏移量,直接使用limit來查詢沒有什麼問題,但隨着數據量的增大,越往後分頁,limit語句的偏移量就會越大,速度也會明顯變慢

二、limit在大數據量下的表現

參考網上一個案例:
表說明:

  • 表名:order,訂單表
  • 字段情況:該表一共37個字段,不包含text等大型數據,最大爲varchar(500),id字段爲索引,且爲遞增
  • 數據量:5709294

select * from order_table where userId = 3 order by id limit 10000,10;

三次查詢時間分別爲:

  • 3040 ms
  • 3063 ms
  • 3018 ms

針對這種查詢方式,下面測試查詢記錄量對時間的影響:

select * from order_table where userId = 3 order by id limit 10000,1;
select * from order_table where userId = 3 order by id limit 10000,10;
select * from order_table where userId = 3 order by id limit 10000,100;
select * from order_table where userId = 3 order by id limit 10000,1000;
select * from order_table where userId = 3 order by id limit 10000,10000;

三次查詢時間如下:

  • 查詢1條記錄:3072ms 3092ms 3002ms

  • 查詢10條記錄:3081ms 3077ms 3032ms

  • 查詢100條記錄:3118ms 3200ms 3128ms

  • 查詢1000條記錄:3412ms 3468ms 3394ms

  • 查詢10000條記錄:3749ms 3802ms 3696ms

在查詢記錄量低於100時,查詢時間基本沒有差距,隨着查詢記錄量越來越大,所花費的時間也會越來越多

針對偏移量的測試:

select * from order_table where userId = 3 order by id limit 100,100;
select * from order_table where userId = 3 order by id limit 1000,100;
select * from order_table where userId = 3 order by id limit 10000,100;
select * from order_table where userId = 3 order by id limit 100000,100;
select * from order_table where userId = 3 order by id limit 100000,100;

三次查詢時間如下:

  • 查詢100偏移:25ms 24ms 24ms

  • 查詢1000偏移:78ms 76ms 77ms

  • 查詢10000偏移:3092ms 3212ms 3128ms

  • 查詢100000偏移:3878ms 3812ms 3798ms

  • 查詢1000000偏移:14608ms 14062ms 14700ms

隨着查詢偏移的增大,尤其查詢偏移大於10萬以後,查詢時間急劇增加

三、limit優化

1. 使用覆蓋索引

select orderType, orderAmt from order_table limit 10000, 10;

假設我們只需要查詢上面兩個字段,可以建立聯合索引(orderType, orderAmt ),這樣可以讓查詢走聯合索引,加快性能。但是我們這個表有37個字段,這麼優化明顯不合適,而且這種方式加快性能方式並不明顯。

2. 假設數據表的id是連續遞增的,可以這樣寫

select * from order_table where id >= 1000000 limit 100;
select * from order_table where id between 10000000 and 1000100 limit 100

直接將對應的offset計算出來,作爲where條件,這樣可以利用主鍵索引,性能提升非常明顯。

上面這種方案存在嚴重問題,數據庫使用自增id,這沒問題,但是,可能存在部分數據被刪除過,也就是id並不連續,所以查出的數據並不是我們想要的。

優化方法:
3. 每次分頁查詢記錄上一次分頁最後一條id
下次查詢:

select * from order_table where id >= 上頁最大id limit 100;

這樣處理的話,id就算不是連續也是沒有問題的,但是必須自增,由於數據庫主鍵我們一般設置成連續自增,所以這種方式可以大幅度優化性能

4. 使用子查詢
上述方法每次都需要記錄上一次分頁的最大id,比較麻煩,我們可以使用子查詢代替:

select * from order_table where id in (select id from order_table limit 1000000, 100);

select * from order_table where id >= (select id from order_table limit 1000000, 1) limit 100;

select * from order_table t1 join (select id from order_table limit 1000000, 1) t2 on t1.id >= t2.id limit 100

這種方式之所以能夠大幅度優化性能,分析:

  • 直接分頁,不會走索引,全表掃描,其實是遍歷主鍵索引樹,但是每次都需要把對應行的數據取出來,要取1000100條數據,然後丟棄前一百萬條,太消耗性能。可以通過explain執行計劃查看對應索引情況

  • 使用子查詢,會走主鍵索引,雖然也是遍歷主鍵索引樹,但是隻需要取id,不需要取出整行數據,最後外層查詢拿到對應的100條id,查詢對應數據即可。通過根據索引字段定位後,大大減少了查詢的數據量,效率自然大大提升

  • 上述使用in,where條件和連接,性能差距不大

5. 業務優化
真的會有人翻到第100萬條數據嗎,一般來說翻頁不會查過20頁,可以通過限制可以翻頁的數量解決這個問題。像百度分頁最多隻能展示76頁。還有一種方式,就是使用滾動,和微博一樣,沒有分頁,只能不斷下拉,就是使用之前記錄上一頁最大offset那個方法就可以做到。ES裏面可以使用scroll API

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