傳統的mysql分頁查詢
select * from table limit n , m
MySQL 執行此類SQL時需要先分頁(默認一頁1000條數據)通過全表掃描到N行,然後再去取M行。對於此類操作,獲取前面少數幾行數據會很快,但是隨着掃描的記錄數越多,SQL的性能就會越差,因爲N的值越大,MySQL需要掃描越多的數據來定位到具體的N行,這樣耗費大量的 IO 成本和時間成本。
特別是上線後數據量積累比較快,必須重視SQL優化,否則影響系統運行和用戶使用體驗
性能實驗
- 直接用limit start, count分頁語句, 也是我程序中用的方法:
select * from table limit start, count
當起始頁較小時,查詢沒有性能問題,我們分別看下從10, 100, 1000, 10000,
30000開始分頁的執行時間(每頁取10條)。
如下:
select * from table limit 10, 10 0.000秒
select * from table limit 100, 10 0.000秒
select * from table limit 1000, 10 0.005秒
select * from table limit 10000,10 0.038秒
select * from table limit 30000, 10 1.160秒
可以告訴大家,做性能試驗的表數據在30W+條,大家可以看出隨着查詢條數的增加,相對查詢所需時間也增加了,這說明分頁語句limit跟起始頁碼是有很大關係的,同樣大家也看到查詢起始頁小時,查詢時間爲0秒,爲什麼會出現這樣的情況?
這需要引入innoDB的緩存池概念
查看InnoDB緩存池大小:
show global variables like ‘innodb_buffer_pool_size’;
-------------------------±------------+
| Variable_name | Value |
±------------------------±------------+
| innodb_buffer_pool_size | 34359738368 |
±------------------------±------------+
默認InnoDB緩存池大小爲128M
InnoDB緩存池用來存儲各種數據的緩存,包括:
數據頁,插入緩存,自適應索引哈希,索引頁,鎖信息,數據字典信息等。
InnoDB緩存池會把查詢過的熱點數據資源加載到緩存池,只要加載過到緩存池的數據,再次查詢的時候直接從緩存池獲取而不需要通過mysql查詢,所以查詢時間會出現0秒的情況.
相關的緩存知識,會單獨開闢一篇博客
言歸正傳,在查詢數據起始頁越多的情況下,查詢效率越慢,我們從緩存池配置知道,通過增加緩存池大小,把經常查詢的數據加載到緩存池,可以起到優化查詢的作用,但是數據本身的查詢效率慢是否可以優化呢?
利用表的覆蓋索引來加速分頁查詢
查看錶索引:
show index from table;
+---------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+---------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| lv_seal | 0 | PRIMARY | 1 | id | A | 318834 | NULL | NULL | | BTREE | | |
| lv_seal | 0 | seal_no | 1 | seal_no | A | 322299 | NULL | NULL | | BTREE | | |
| lv_seal | 1 | name | 1 | name | A | 292690 | NULL | NULL | YES | BTREE | | |
| lv_seal | 1 | lv_seal_ik1 | 1 | uuid | A | 305924 | NULL | NULL | YES | BTREE | | |
| lv_seal | 1 | lv_seal_ik2 | 1 | status | A | 9 | NULL | NULL | YES | BTREE | | |
| lv_seal | 1 | lv_seal_ik3 | 1 | create_date | A | 248352 | NULL | NULL | YES | BTREE | | |
| lv_seal | 1 | lv_seal_ik4 | 1 | update_date | A | 245174 | NULL | NULL | YES | BTREE | | |
+---------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
我們都知道,利用了索引查詢的語句中如果只包含了那個索引列(覆蓋索引),那麼這種情況會查詢很快。
因爲利用索引查找有優化算法,且數據就在查詢索引上面,不用再去找相關的數據地址了,這樣節省了很多時間。加上Mysql中的索引緩存,在併發高的時候利用緩存就效果更好了。
在我們的例子中,我們知道id字段是主鍵,自然就包含了默認的主鍵索引。現在讓我們看看利用覆蓋索引的查詢效果如何。
這次我們之間查詢最後一頁的數據(利用覆蓋索引,只包含id列),如下:
select id from table limit 300000,10 0.09秒
相對於查詢了所有列的1.16秒,提升了很多
那麼如果我們也要查詢所有列,有兩種方法,
方式一:d>=的形式,
SELECT * FROM table WHERE ID > =(select id from table limit 300000, 1) limit 10
查詢時間爲0.09秒!
方式二:通過內鏈接查詢
SELECT * FROM table a JOIN (select id from table limit 300000, 10) b ON a.ID = b.id
查詢時間0.10也很短!