問題
mysql使用limit分頁,當limit offset,rows的offset數值過大時,會出現效率問題。
準備數據
正常查詢:
select * from t_bank where bank_code like '0%' limit 145000,5;
查詢三次,時間分別是0.684s,0.901s,0.708s.
執行計劃,走的range索引。
優化方法
select * from t_bank JOIN (select id from t_bank where bank_code like '0%' limit 145000,5) a on a.id= t_bank.id;
查詢三次,耗時分別爲0.151s,0.401s,0.211s。
分析
爲什麼這樣寫可以提高效率?
select * from t_bank where bank_code like ‘0%’ limit 145000,5; 的查詢過程:
- 找到非聚簇索引葉子節點上的主鍵值;
- 根據主鍵值去聚簇索引上查詢需要的全部字段值。
- 葉子節點:指InnoDB索引(數據結構:B+Tree,由二叉查找樹,平衡二叉樹,B樹演化而來)的一個存儲單元。
- 聚簇索引:又稱聚集索引,InnoDB的表一定有一個聚簇索引,有主鍵就是主鍵,沒有主鍵就會選擇一個唯一非空索引,沒有唯一非空索引就會隱式創建一個聚簇索引。
- 聚集索引(主鍵索引)的葉子節點存的是索引key(主鍵)和數據行,非聚集索引的葉子節點存的是索引key和主鍵鍵值。
由此可以看出,非聚簇索引的查詢有兩個過程。而limit查詢,偏移的所有數據,都會走着兩個過程。如果只查id,就可以省略第2個步驟。
證實
爲了證實select * from t_bank where bank_code like ‘0%’ limit 145000,5是掃描了145005個索引節點和145005個聚簇索引節點,只能用間接的方法來證明。
InnoDB有個buffer pool,裏面存有最近訪問的數據頁,包括數據頁和索引頁。我們需要運行兩個SQL,比較buffer pool裏數據頁的數量即可得到結果。
mysql> select index_name,count(*) from information_schema.INNODB_BUFFER_PAGE where INDEX_NAME in('val','primary') and TABLE_NAME like '%test%' group by index_name;
遇到的問題
爲了在每次重啓時確保清空buffer pool,我們需要關閉innodb_buffer_pool_dump_at_shutdown和innodb_buffer_pool_load_at_startup,這兩個選項能夠控制數據庫關閉時dump出buffer pool中的數據和在數據庫開啓時載入在磁盤上備份buffer pool的數據。