大部分開發和DBA同行都對分頁查詢很是很是瞭解,看帖子翻頁須要分頁查詢,搜索商品也須要分頁查詢。那麼問題來了,遇到上千萬或者上億的數據量怎麼快速的拉取全量,好比大商家拉取每個月千萬級別的訂單數量到本身獨立的ISV作財務統計;或者擁有百萬千萬粉絲的公衆大號,給所有粉絲推送消息的場景。本文講講我的的優化分頁查詢的經驗,拋磚引玉。前端
在講如何優化以前咱們先來看看一個比較常見錯誤的寫法sql
SELECT * FROM table
where kid=1342 and type=1 order id asc limit 149420,20;
該SQL是一個很是典型的排序+分頁查詢:緩存
order by col limit N,OFFSET M
MySQL 執行此類SQL時須要先掃描到N行,而後再去取 M行。對於此類操做,取前面少數幾行數據會很快,可是掃描的記錄數越多,SQL的性能就會越差,由於N越大,MySQL須要掃描越多的數據來定位到具體的N行,這樣耗費大量的IO 成本和時間成本。一圖勝千言,咱們使用簡單的圖來解釋爲何 上面的sql 的寫法掃描數據會慢。
t 表是一個索引組織表,key idx_kid_type(kid,type) 。性能
符合kid=3 and type=1 的記錄有不少行,咱們取第 9,10行。優化
select * from t where kid =3 and type=1 order by id desc 8,2;
MySQL 是如何執行上面的sql 的?對於Innodb表,系統是根據 idx_kid_type 二級索引裏面包含的主鍵去查找對應的行。對於百萬千萬級別的記錄而言,索引大小可能和數據大小相差無幾,cache在內存中的索引數量有限,並且二級索引和數據葉子節點不在同一個物理塊兒上存儲,二級索引與主鍵的相對無序映射關係,也會帶來大量的隨機IO請求,N值越大越須要遍歷大量索引頁和數據葉,須要耗費的時間就越久。網站
鑑於上面的大分頁查詢耗費時間長的緣由,咱們思考一個問題,是否須要徹底遍歷「無效的數據」?若是咱們須要limit 8,2;咱們跳過前面8行無關的數據頁遍歷,能夠直接經過索引定位到第9,第10行,這樣操做是否是更快了?依然是一圖勝千言,經過這其實也是 延遲關聯的 核心思思:經過使用覆蓋索引查詢返回須要的主鍵,再根據主鍵關聯原表得到須要的數據,而不是經過二級索引獲取主鍵再經過主鍵去遍歷數據頁。url
經過上面的原理分析,咱們知道經過常規方式進行大分頁查詢慢的緣由,也知道了提升大分頁查詢的具體方法 ,下面咱們討論一下在線上業務系統中經常使用的解決方法。spa
針對limit 優化有不少種方式:code
3.1 延遲關聯
優化前blog
root@xxx 12:33:48>explain SELECT id, cu_id, name, info, biz_type, gmt_create, gmt_modified,start_time, end_time, market_type, back_leaf_category,item_status,picuture_url FROM relation where biz_type ='0' AND end_time >='2014-05-29' ORDER BY id asc LIMIT 149420 ,20; +----+-------------+-------------+-------+---------------+-------------+---------+------+--------+-----+ | id | select_type | table | type | possible_keys | key | key\_len | ref | rows | Extra | +----+-------------+-------------+-------+---------------+-------------+---------+------+--------+-----+ | 1 | SIMPLE | relation | range | ind\_endtime | ind\_endtime | 9 | NULL | 349622 | Using where; Using filesort | +----+-------------+-------------+-------+---------------+-------------+---------+------+--------+-----+ 1 row in set (0.00 sec)
其執行時間:
優化後:
root@xxx 12:33:43>explain SELECT a.* FROM relation a, (select id from relation where biz_type ='0' AND end\_time >='2014-05-29' ORDER BY id asc LIMIT 149420 ,20 ) b where a.id=b.id; +----+-------------+-------------+--------+---------------+---------+---------+------+--------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------------+--------+---------------+---------+---------+------+--------+-------+ | 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL | 20 | | | 1 | PRIMARY | a | eq_ref | PRIMARY | PRIMARY | 8 | b.id | 1 | | | 2 | DERIVED | relation | index | ind_endtime | PRIMARY | 8 | NULL | 733552 | | +----+-------------+-------------+--------+---------------+---------+---------+------+--------+-------+ 3 rows in set (0.36 sec)
執行時間:
優化後 執行時間 爲原來的1/3 。
首先要獲取複合條件的記錄的最大 id和最小id(默認id是主鍵)
select max(id) as maxid ,min(id) as minid from t where kid=2333 and type=1;
其次 根據id 大於最小值或者小於最大值 進行遍歷。
select xx,xx from t where kid=2333 and type=1 and id >=min_id order by id asc limit 100;
select xx,xx from t where kid=2333 and type=1 and id <=max_id order by id desc limit 100;
案例
當遇到延遲關聯也不能知足查詢速度的要求時
SELECT a.id as id, client_id, admin_id, kdt_id, type, token, created_time, update_time, is_valid, version FROM t1 a, (SELECT id FROM t1 WHERE 1 and client_id = 'xxx' and is_valid = '1' order by kdt_id asc limit 267100,100 ) b WHERE a.id = b.id;
使用延遲關聯查詢數據510ms ,使用基於書籤模式的解決方法減小到10ms之內 絕對是一個質的飛躍。
SELECT * FROM t1
where client_id='xxxxx' and is_valid=1 and id<47399727 order by id desc LIMIT 100;
從咱們的優化經驗和案例上來說,根據主鍵定位數據的方式直接定位到主鍵起始位點,而後過濾所須要的數據 相對比延遲關聯的速度更快些,查找數據的時候少了二級索引掃描。可是 優化方法沒有銀彈,沒有一勞永逸的方法。好比下面的例子
order by id desc 和 order by asc 的結果相差70ms ,生產上的案例有limit 100 相差1.3s ,這是爲何呢?留給你們去思考吧。
最後,其實我相信還有其餘優化方式,好比在使用不到組合索引的所有索引列進行覆蓋索引掃描的時候使用 ICP 的方式 也可以加快大分頁查詢。
以上是我在優化分頁查詢方面的經驗總結,拋磚引玉,有興趣的朋友能夠多交流,分享大家的優化經驗案例。