Mysql大範圍分頁優化案例

在BBS線上業務抓到以下分頁SQL:mysql

142597301   meizu_bbs   192.168.17.72:39096 meizu_bbs   Query   217 Sending data    SELECT * FROM pre_forum_thread  WHERE fid=22 AND displayorder>=0 ORDER BY lastpost DESC  LIMIT 1933100, 50
142597338   meizu_bbs   192.168.17.72:39128 meizu_bbs   Query   216 Sending data    SELECT * FROM pre_forum_thread  WHERE fid=22 AND displayorder>=0 ORDER BY lastpost DESC  LIMIT 1933100, 50
142604367   nagiosuser  127.0.0.1:39893 NULL    Query   0   NULL    show full processlist

這個SQL一共有3個問題:ios

1:select * 這種寫法不符合SQL編寫規範,任什麼時候候都不要用*來代替具體的列名稱,須要什麼列就取什麼列。若是表裏有個text/blob等大字段,影響就更加明顯。sql

2:pre_forum_thread 表在tid字段作了分區,可是查詢裏面沒有用到分區字段,這樣就須要掃描所有分區,性能比不分區更差。數據庫

3:在這個分頁SQL裏,偏移量高到193萬。post

LIMIT語法:性能

[LIMIT {[offset,] row_count | row_count OFFSET offset}]

MYSQL是處理LIMIT語句的方式是:取出所有offset+rowcount,而後丟棄掉前面全部行,只返回row_count行。優化

在這個案例裏,在mysql server端須要查詢的行數爲1933100+50,217S還未得出結果。spa

優化方案: 最終須要的只是50行記錄,若是先取出這50行記錄的主鍵ID,這樣是否是會很快? 執行計劃和執行時間:code

mysql> explain partitions SELECT tid FROM pre_forum_thread  WHERE fid=22 AND displayorder>=0 ORDER BY lastpost DESC  LIMIT 1933100, 50;
+----+-------------+------------------+-----------------------------------------------------------------------------------------------------------+-------+-------------------------------------------------------------------------------+--------------+---------+------+---------+------------------------------------------+
| id | select_type | table            | partitions                                                                                                | type  | possible_keys                                                                 | key          | key_len | ref  | rows    | Extra                                    |
+----+-------------+------------------+-----------------------------------------------------------------------------------------------------------+-------+-------------------------------------------------------------------------------+--------------+---------+------+---------+------------------------------------------+
|  1 | SIMPLE      | pre_forum_thread | p0,p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,p11,p12,p13,p14,p15,p16,p17,p18,p19,p20,p21,p22,p23,p24,p25,p26,p27,p28 | range | displayorder,rate,lastpost,fd,fdd,idx_fid_displayorder_heats,idx_displayorder | displayorder | 4       | NULL | 2673718 | Using where; Using index; Using filesort |
+----+-------------+------------------+-----------------------------------------------------------------------------------------------------------+-------+-------------------------------------------------------------------------------+--------------+---------+------+---------+------------------------------------------+
1 row in set (0.00 sec)
 
mysql> SELECT sql_no_cache tid FROM pre_forum_thread  WHERE fid=22 AND displayorder>=0 ORDER BY lastpost DESC  LIMIT 1933100, 50;
+--------+
| tid    |
+--------+
| 795442 |
.........
| 795387 |
| 795168 |
+--------+
50 rows in set (1.02 sec)

分析一下爲何只取出PK值會很快。 在INNODB索引樹裏面,每一個二級索引的葉子節點都會保存一份PK值,經過二級索引查找數據的過程是:從索引樹的根節點開始比較索引值是否和查詢值匹配,若是不匹配,根據狀況進入左或右分支,再比較,直到在找到符合要求的節點,而後從葉節點裏取出PK值,再回表根據主鍵獲得所有數據。若是隻是查找主鍵,那麼就少了」而後從葉節點裏取出PK值,再回表根據主鍵獲得所有數據「這一部分,而這一部分正是最耗時的。在執行計劃裏能夠看到」Using index「,這就說明優化器使用了」覆蓋索引「,只須要掃描索引數據就能夠獲得最終數據,索引通常狀況下比數據要小,每每常駐內存,因此雖然偏移量193萬,也能給在1.02秒內返回結果。server

獲得這50個主鍵ID值以後,用這50條記錄再關聯原表查詢:

mysql> explain select sql_no_cache * from pre_forum_thread A inner join (SELECT tid FROM pre_forum_thread  WHERE fid=22 AND displayorder>=0 ORDER BY lastpost DESC  LIMIT 1933100, 50) B on A.tid=B.tid;
+----+-------------+------------------+--------+-------------------------------------------------------------------------------+---------+---------+-------+---------+----------------+
| id | select_type | table            | type   | possible_keys                                                                 | key     | key_len | ref   | rows    | Extra          |
+----+-------------+------------------+--------+-------------------------------------------------------------------------------+---------+---------+-------+---------+----------------+
|  1 | PRIMARY     | <derived2>       | ALL    | NULL                                                                          | NULL    | NULL    | NULL  |      50 |                |
|  1 | PRIMARY     | A                | eq_ref | PRIMARY                                                                       | PRIMARY | 4       | B.tid |       1 |                |
|  2 | DERIVED     | pre_forum_thread | ALL    | displayorder,rate,lastpost,fd,fdd,idx_fid_displayorder_heats,idx_displayorder | NULL    | NULL    | NULL  | 3307262 | Using filesort |
+----+-------------+------------------+--------+-------------------------------------------------------------------------------+---------+---------+-------+---------+----------------+
3 rows in set (1.03 sec)
 
#執行時間
50 rows in set (1.06 sec)

處理分頁的方法有不少種,在業務層面能夠限制翻頁的起始位置,不容許直接定位到10000頁。在數據庫查詢方面也有別的方法來處理,根據相應的業務須要尋找最佳的處理方法。 本案例裏的 LIMIT 1933100, 50 須要規避。

相關文章
相關標籤/搜索