在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 須要規避。