MySQL - 分页查询优化的两个案例解析
文章目錄
- 生猛干貨
- Table
- 日常場景
- Case1 根據自增且連續的主鍵排序的分頁查詢
- 優化
- 數據可刪除的場景
- 適用條件
- Case2 根據非主鍵字段排序的分頁查詢
- 搞定MySQL
生猛干貨
帶你搞定MySQL實戰,輕松對應海量業務處理及高并發需求,從容應對大場面試
Table
還是我們那個老表
CREATE TABLE `employees` (`id` int(11) NOT NULL AUTO_INCREMENT,`name` varchar(24) NOT NULL DEFAULT '' COMMENT '姓名',`age` int(11) NOT NULL DEFAULT '0' COMMENT '年齡',`position` varchar(20) NOT NULL DEFAULT '' COMMENT '職位',`hire_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '入職時間',PRIMARY KEY (`id`),KEY `idx_name_age_position` (`name`,`age`,`position`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='員工記錄表';有個主鍵索引和二級聯合索引 idx_name_age_position
日常場景
任何一個系統,分頁查詢都是必不可少的吧 ,MySQL中的分頁查詢 就是 limit唄 ,你有沒有感覺到 越往后翻頁越慢 ,常見的SQL如下
mysql> select * from employees limit 10000,10;就是從 employees 中取出從 10001 行開始的 10 行記錄。
MySQL是怎么處理這個SQL的呢?
先讀取 10010 條記錄,然后拋棄前 10000 條記錄,僅保留10 條想要的數據 。 可想而知,如果要查詢一張大表比較靠后的數據,這效率是非常低的。
那有沒有優化的辦法呢?
Case1 根據自增且連續的主鍵排序的分頁查詢
我們先來看一個 【根據自增且連續主鍵排序的分頁查詢】的優化案例
select * from employees limit 10000, 10從第1萬條數據開始,獲取10條數據
我們來看下執行計劃
mysql> explain select * from employees limit 10000, 10 ;+----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+-------+ | 1 | SIMPLE | employees | NULL | ALL | NULL | NULL | NULL | NULL | 100175 | 100 | NULL | +----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+-------+ 1 row in setmysql>因為沒有添加單獨 order by字段,所以表示通過主鍵排序 。 執行計劃顯示全表掃描
優化
如何優化下呢?
既然是按照id排序,結合B+Tree 的特性 ,如果能從 10000這個數據位置往后掃描,是不是就會比掃描全部理論上更快一些呢?
改造如下
select * from employees where id> 10000 limit 10;來看下執行計劃
mysql> explain select * from employees where id> 10000 limit 10; +----+-------------+-----------+------------+-------+---------------+---------+---------+------+-------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-----------+------------+-------+---------------+---------+---------+------+-------+----------+-------------+ | 1 | SIMPLE | employees | NULL | range | PRIMARY | PRIMARY | 4 | NULL | 50087 | 100 | Using where | +----+-------------+-----------+------------+-------+---------------+---------+---------+------+-------+----------+-------------+ 1 row in set比一比這兩個,是不是下面那個更快一些
數據可刪除的場景
還有個問題,我們知道我們業務系統有些數據是可以被刪除的,如果有些數據被刪除了,還是按照id來排序,上面這種優化方式,會存在問題嗎?
假設8888 這條業務數據被刪除了
delete from employees where id = 8888 ;那我們來看下
如果允許刪除,那這種優化方式是不是就不正確了?
-
limit 10000, 10 : 就是全部數據排好序后 取第10000個開始后的10個,我們剛才刪除了8888, 所以 第一條數據就變成了 10002
-
id> 10000 limit 10 : 這個就很好理解了,刪除了8888 ,不影響 id>10000的排序 ,所以第一條數據還是 10001
適用條件
如果主鍵不連續,不能使用上面描述的優化方法。
如果原 SQL 是 order by 非主鍵的字段,按照上的方法改寫會導致兩條 SQL 的結果不一致。
所以這種優化方式必須同時滿足以下兩個條件:
- 主鍵自增且連續
- 結果是按照主鍵排序的
Case2 根據非主鍵字段排序的分頁查詢
來看第二個案例,實際工作中可能比第一種用的比較多
select * from employees ORDER BY name limit 10000, 10 ;來看下執行計劃
mysql> explain select * from employees ORDER BY name limit 10000, 10 ;+----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+----------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+----------------+ | 1 | SIMPLE | employees | NULL | ALL | NULL | NULL | NULL | NULL | 100175 | 100 | Using filesort | +----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+----------------+ 1 row in setmysql>按照B+Tree的結構,應該會走name字段索引,wtf , 操作的結果集太多,又要回表等等原因 , MySQL可能不選name 字段的索引 , key 字段對應的值為 null ,從而走了全表掃描 。。。。
還有 Using filesort
這部分就屬于MySQL內部的優化了,可以使用Trace來追蹤下MySQL是如何選擇的 ,
MySQL - 使用trace工具來窺探MySQL是如何選擇執行計劃的
MySQL認為掃描整個索引并查找到沒索引的行(可能要遍歷多個索引樹)的成本比掃描全表的成本更高,所以優化器放棄使用索引。
那既然知道不走索引的原因,那么怎么優化呢?
關鍵是讓排序時返回的字段盡可能少,所以可以讓排序和分頁操作先查出主鍵,然后根據主鍵查到對應的記錄.
讓排序時返回的字段盡可能少–》 只返回id , 然后用返回的特定范圍的id ,再和原表關聯,只取特定范圍內的數據 ,肯定比全表掃描要快。
改造如下
select * from employees a inner join (select id from employees order by name limit 10000,10) b on a.id = b.id;先找到id (select id 使用覆蓋索引),然后用這個結果集 (這個案例中就只有10條結果)去和 employees 關聯
看看執行計劃
原 SQL 使用的是 filesort 排序,優化后的 SQL 使用的是索引排序。
當然了,結果集也是和優化前是一致的
搞定MySQL
總結
以上是生活随笔為你收集整理的MySQL - 分页查询优化的两个案例解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MySQL - order by和 gr
- 下一篇: MySQL - Join关联查询优化 -