如題,年前做了一個需求,涉及到Mysql大分頁查詢,整理一下,希望對需要的小伙伴有幫助。
背景分頁查詢的性能瓶頸B+樹簡述B+比起二叉查找樹,有什么優勢?分頁查詢過程測試集解決方法1 延遲關聯法:2 主鍵閾值法最后
背景

系統結構如上圖。經過排查是因為 系統B拉取數據時間太長導致的推送超時。
系統B拉取數據的方法是根據_tiemstamp(數據操作時間)分頁查詢系統A的接口,即:
1SELECT 字段名
2FROM 表名
3WHERE _timestamp >= beginTime AND _timestamp <= endTime
4LIMIT n, m;
由於該數據是從其他數據源中導入的,所以_timestamp這個字段值幾乎相同,這就導致了在我們的查詢范圍內存在大約150萬的數據。一般遇到這種情況,首先想到的就是是否需要給_timestamp添加索引,這張表上是存在_timestamp索引的。那么為什么還會出現這個問題呢?這就要從分頁查詢本身說起了。
分頁查詢的性能瓶頸
B+樹簡述
首先我們要了解InnoDB存儲引擎中的B+數索引。這里我簡單總結一下:

上圖是一顆B+樹,通過觀察我們可以發現它的一些特點:
1.每個節點中子節點個個數不能少於m/2個,不能大於m個(B+樹是一顆m叉樹,圖中m=3)
2.根節點的節點個數可以超過m/2個,這是一個例外
上述兩點特性是為了保證B+樹的查詢效率。
節點數超過m越多,在總節點數相同的情況下,樹的高度h就越小,此時m叉數就會向鏈表退化(O(logn)->O(n))。 節點數小於m/2越多,在總節點數相同的情況下,樹的高度h就越高,此時查詢數據,就需要經歷更多次的IO
3.m叉樹非葉子節點只存儲索引,不存儲數據
4.通過鏈表將葉子節點串聯在一起,這樣可以方便按區間查找。
B+比起二叉查找樹,有什么優勢?
更矮,這就減少了IO次數。
由於非葉子節點不存儲數據,上圖查詢任何數據,都需要3次IO,查詢性能更穩定
由於葉子節點使用了鏈表連接,范圍查詢更簡便。
分頁查詢過程
1.首先通過非主鍵索引查詢出所有條件的主鍵
2.通過主鍵索引,定位到數據
3.不斷重復上述操作
4.根據分頁條件,確定返回數據的啟始位置以及數據量
5.返回數據
可以看出,初始位置值越大,定位時需要查詢的數據就越多,查詢效率也會越低
測試集
為了測試優化效果,我准備了150萬測試數據(需要跑幾分鍾)。
1# 建表語句
2CREATE TABLE `test`(
3 `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵',
4 `name` varchar(512) NOT NULL DEFAULT '無' COMMENT '創建人',
5 `_timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間',
6 PRIMARY KEY (`id`),
7 KEY `ix_timestamp` (`_timestamp`)
8) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='測試表';
9
10
11# 通過存儲過程導入數據
12drop procedure idata;
13delimiter ;;
14create procedure idata()
15begin
16 declare i int;
17 set i=1;
18 while(i<=1500000)do
19 insert into test values(i, i, now());
20 set i=i+1;
21 end while;
22end;;
23delimiter ;
24
25call idata();
接着,我們看一下使用索引的情況下,分頁查詢語句的耗時情況。

可以看出,在使用索引的情況下,無論初始位置是0,還是145萬,Mysql都會掃描所有符合條件的數據,然后找到初始位置的數據,向后查偏移量個數據,最后返回。


這兩條語句的執行速度差距非常大,大約3個數量級(0.00sec,10 sec)
解決方法
針對於limit,有很多優化的方法,比如前端加緩存、或者使用分頁加載的方式展示數據。(大部分用戶請求數據的初始開始都不會很大)。在我們的使用場景中,調大超時時間的閾值也是可以的。
但是回到問題本身,問題出現的原因就是分頁語句隨着初始位置的增加,會有性能問題,所以治本的辦法,是對這個語句進行優化,有兩個優化方法:
1 延遲關聯法:
我們先查詢出符合要求的主鍵(由於查詢的字段有索引,該索引的葉子節點就是主鍵,通過索引覆蓋我們可以省去一次回表操作。)
然后再通過主鍵索引查詢數據,這就省去了遍歷數據找初始位置數據的過程

通過延遲關聯的方法,我們將10sec的耗時降低到了1.58sec,優化了將近1個數量級。
2 主鍵閾值法
如果你的主鍵是自增的,那么就可以通過條件推算出符合條件的主鍵最大值&最小值(這里也是通過索引覆蓋省去了一次回表操作)
然后再根據閾值,取數據即可,同樣省去了遍歷數據找初始位置數據的過程

通過主鍵閾值法的方法,我們將10sec的耗時降低到了1.12sec,優化了1個數量級
最后
最后對文章做一下補充說明:
1.文中優化效果是僅憑借調用一次SQL的耗時給出的,並不科學,僅僅是為了讓大家有一個直觀的概念。
2.無論是延遲關聯法,還是主鍵閾值法。思想都是一樣的,先把符合條件的主鍵找到,然后通過主鍵去定位符合條件的數據,這里優化了2個點:1.通過索引覆蓋避免了回表;2.通過主鍵直接定位數據的方法,省去了在數據集中查詢初始位置的過程
3.優化的效果隨數據量增加而增強。萬級別的數據優化效果可能並不明顯。
最后,期待您的訂閱和點贊,專欄每周都會更新,希望可以和您一起進步,同時也期待您的批評與指正!
