mysql大表分頁查詢優化(翻譯)

大表分頁查詢優化


原文地址:https://www.slideshare.net/suratbhati/efficient-pagination-using-mysql-6187107

1. 分頁查詢核心點

– 使用索引來過濾 rows (解決通過 WHERE)
– 使用相同的索引按排序順序返回行 (解決通過 ORDER)

參考資料:
http://dev.mysql.com/doc/refman/5.1/en/mysql-indexes.html
http://dev.mysql.com/doc/refman/5.1/en/order-by-optimization.html
http://dev.mysql.com/doc/refman/5.1/en/limit-optimization.html

2 索引使用

建立索引 a_b_c (a, b, c)

ORDER 將會使用索引

– ORDER BY a 
– ORDER BY a,b 
– ORDER BY a, b, c 
– ORDER BY a DESC, b DESC, c DESC 

WHERE 和 ORDER 都將使用索引:

– WHERE a = const ORDER BY b, c 
– WHERE a = const AND b = const ORDER BY c 
– WHERE a = const ORDER BY b, c 
– WHERE a = const AND b > const ORDER BY b, c 

ORDER 將不會使用索引 (文件排序)

– ORDER BY a ASC, b DESC, c DESC /* 多種排序方向 */ 
– WHERE g = const ORDER BY b, c /* a 前綴不存在 */ 
– WHERE a = const ORDER BY c /* b 丟失 */ 
– WHERE a = const ORDER BY a, d /* d 不在索引中 */ 

3. 使用實例

我們將通過兩種使用情景來分析分頁:
• 通過時間分頁, 最近的消息排序分頁 可以直接使用id代替
• 通過thumps_up(投票數), 點贊數多的排前面

3.1 表結構

 CREATE TABLE `message` (
  `id` int(11) NOT NULL AUTO_INCREMENT, 
  `title` varchar(255) COLLATE utf8_unicode_ci NOT NULL, 
  `user_id` int(11) NOT NULL, 
  `content` text COLLATE utf8_unicode_ci NOT NULL, 
  `create_time` int(11) NOT NULL, 
  `thumbs_up` int(11) NOT NULL DEFAULT 0, /* Vote Count */ 
  PRIMARY KEY (`id`), 
  KEY `thumbs_up_key` (`thumbs_up`,`id`)
 ) ENGINE=InnoDB 

查看錶相關信息

 mysql> show table status like message \G;

  Engine: InnoDB 
  Version: 10 
  Row_format: Compact 
  Rows: 50000040 /* 50 Million */ 
  Avg_row_length: 565 
  Data_length: 28273803264 /* 26 GB */ 
  Index_length: 789577728 /* 753 MB */ 
  Data_free: 6291456 
  Create_time: 2009-04-20 13:30:45 

3.2 典型的查詢

3.2.1 獲取總的記錄數
SELECT count(*) FROM message 

3.2.2 獲取當前頁信息

SELECT * FROM message ORDER BY id DESC LIMIT 0, 20 

注意: id 是自增的, 和 create_time 順序相同, 不需要在create_time創建索引, 節省空間

3.3 查看性能

mysql> explain SELECT * FROM message ORDER BY id DESC LIMIT 10000, 20 \G;

 ***************** 1. row ************** 
 id: 1 
 select_type: SIMPLE 
 table: message 
 type: index 
 possible_keys: NULL 
 key: PRIMARY 
 key_len: 4 
 ref: NULL 
 rows: 10020 
 Extra: 

 1 row in set (0.00 sec) 

– 能使用索引掃描和讀取行並在找到需要的行數後停止.
– LIMIT 10000, 20 意味着他需要讀取 10020 行,並且拋棄前 10000 行, 然後返回接下來的 20 行.

3.4 性能影響

– 非常大的 OFFSET 將會帶來數據集的顯著增長, MySQL 將攜帶數據到內存並不會返回給調用者
– 當您擁有不適合主內存的數據庫時,性能問題會更加明顯.
– 使用大型OFFSET的小百分比請求將能夠達到磁盤 I/O 瓶頸
– 爲了顯示 1000,000 行數據中的 21 - 40數據 , 有時候需要計算1000,000行.

4. 解決方案

4.1 簡單解決方案

– 不要顯示中的記錄數, 用戶真的會關心嗎?
– 不要讓用戶跳轉到更深層的頁碼所在的頁面

4.2 避免使用Count(*)

  1. 絕不顯示總的信息, 讓用戶查看更多信息通過點擊next
  2. 不要計算每個請求, 緩存他, 顯示陳舊的計數, 用戶不會關心是 324533 還是 324633
  3. 顯示 41 to 80 of Thousands
  4. 使用預先計算(觸發器或緩存), insert/delete 發生時自增或自減相應的值.

更多參考:https://blog.csdn.net/wujiangwei567/article/details/88721395

4.3 避免使用offset

我們要改變用戶習慣:

– 不要直接跳轉到第 N 頁
LIMIT N 是好的, 不要使用 LIMIT M,N
– 提供給定起始頁面的額外where條件
– 使用給定的線索和更多的WHERE條件 以及ORDER BY和不帶OFFSETLIMIT N

5 使用更多條件加快搜索

5.1 創建時間分頁

比如說每頁顯示5條信息 對應的頁碼,列表id和相應的上一頁下一頁鏈接如下

頁碼 信息id數 鏈接
第一頁 150 111 102 101 100 Next: page=2&last_seen=100&dir=next
第二頁 98 97 96 95 94 Prev: page=1&last_seen=98&dir=prev Next: page=3&last_seen=94&dir=next
第三頁 93 92 91 90 89 Prev: page=2&last_seen=93&dir=prev Next: page=4&last_seen=89&dir=next

根據頁碼信息,我們是按照時間排序,而創建時間和id的順序是一致的,所以我們可以進行如下篩選來達到
更快的查詢體驗

Next(下一頁)

WHERE id < 100  /* 通過參數 last_seen 獲取 */ 
ORDER BY id DESC LIMIT 5 /* 沒有 OFFSET 偏移量,顯示5條數據 */ 

Prev(前一頁)

WHERE id > 98  /* 通過參數 last_seen 獲取 */ 
ORDER BY id ASC LIMIT 5 /* 沒有 OFFSET 偏移量,顯示5條數據 */ 

這裏前一頁的數據返回時如果按照最近的思想,我們需要對結果集做一個倒序的排序

性能解析:

mysql> explain SELECT * FROM message WHERE id < 49999961 ORDER BY id DESC LIMIT 20 \G; 

*************************** 1. row *************************** 
id: 1 
select_type: SIMPLE 
table: message 
type: range 
possible_keys: PRIMARY 
key: PRIMARY 
key_len: 4 
ref: NULL 
Rows: 25000020 /* ignore this */ 
Extra: Using where 

1 row in set (0.00 sec) 

5.2 投票數分頁

我們假設 每頁的投票數和對應的鏈接如下:

頁碼 投票數 鏈接
第一頁 99 99 98 98 98 Next: page=2&last_seen=100&dir=next
第二頁 98 98 97 97 10 Prev: page=1&last_seen=98&dir=prev Next: page=3&last_seen=94&dir=next

我們不能使用:

WHERE thumbs_up < 98 ORDER BY thumbs_up DESC /* 他將會返回很少的行 */ 

那我們可以這樣做麼:

WHERE thumbs_up <= 98 AND <額外的條件> ORDER BY thumbs_up DESC 
5.2.1 尋找額外的條件

• 考慮thumbs_up作爲主要的編號
– 如果我們有額外的次要編號,我們可以使用major和minor的組合作爲額外條件

• 找到額外的列 (次要編號)
– 我們可以使用id主鍵作爲次要編號

5.2.2 解決方案

第一頁

mysql> SELECT thumbs_up, id FROM message ORDER BY thumbs_up DESC, id DESC LIMIT 5
 +-----------+----+
 | thumbs_up | id | 
 +-----------+----+ 
 | 99 | 14 | 
 | 99 | 2  | 
 | 98 | 18 | 
 | 98 | 15 | 
 | 98 | 13 | 
 +-----------+----+ 

Next(下一頁)

 mysql> SELECT thumbs_up, id FROM message WHERE thumbs_up <= 98 AND (id < 13 OR thumbs_up < 98) ORDER BY thumbs_up DESC, id DESC LIMIT 5 

 +-----------+----+ 
 | thumbs_up | id | 
 +-----------+----+ 
 | 98 | 10 | 
 | 98 | 6  |
 | 97 | 17 | 
 +-----------+----+  

解釋一下子: 我們這裏投票數 thumbs_up 相同的前提下 id做的是倒序排列,那麼如果是相同的thumbs_up
下一頁的id肯定小於 13 或者 thumbs_up 小於當前數

5.2.3 更好的方式
 SELECT * 
 FROM message 
 WHERE thumbs_up <= 98 AND (id < 13 OR thumbs_up < 98) 
 ORDER BY thumbs_up DESC, id DESC LIMIT 20 

改寫爲:

SELECT m2.* 
FROM message m1, message m2 
WHERE m1.id = m2.id AND m1.thumbs_up <= 98 AND (m1.id < 13 OR m1.thumbs_up < 98) 
ORDER BY m1.thumbs_up DESC, m1.id DESC LIMIT 20;

我們看一下性能分析

*************************** 1. row *************************** 
id: 1 
select_type: SIMPLE 
table: m1 
type: range possible_keys: PRIMARY,thumbs_up_key 
key: thumbs_up_key /* (thumbs_up,id) */ 
key_len: 4 
ref: NULL 
Rows: 25000020 /*忽略這個 我們僅僅讀 20 行*/ 
Extra: Using where; Using index /* Cover */ 

*************************** 2. row *************************** 
id: 1 
select_type: SIMPLE 
table: m2 
type: eq_ref 
possible_keys: PRIMARY 
key: PRIMARY 
key_len: 4 
ref: forum.m1.id 
rows: 1 
Extra: 

我們僅僅做了一個全連接,m2 僅僅掃描了1行 也是用了key和對應的索引

有的人說使用

SELECT *
FROM
    message
WHERE
    (thumbs_up,id) > (98,14)
ORDER BY thumbs_up, id
LIMIT 5

這樣寫確實可以提高效率,但是仍然沒有上面的兩種寫法快

6. 爲什麼不能使用offest

主鍵排序 和 次鍵排序 如果使用offest 都會顯著降低性能

在這裏插入圖片描述

在這裏插入圖片描述
通過統計圖表可以看出:
– Using LIMIT OFFSET, N
• 600 query/sec

– Using LIMIT N (no OFFSET)
• 3.7k query/sec

7. google在做什麼

. 估計有多少結果。再一次,谷歌這樣做,沒有人抱怨
在這裏插入圖片描述

. 不要顯示所有結果。甚至谷歌也不會讓你看到百萬分之一的結果。
在這裏插入圖片描述

. 不顯示總計數或其他頁面的中間鏈接。僅顯示“下一個”鏈接。

在這裏插入圖片描述

碼字不易,打賞一下小弟吧

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