MySQL带排序的分页查询优化
需求
在日常开发中,经常会遇到这么一种情况,一张表的数据量较大(500万左右)的时候,对其进行分页查询的时候,在分页比较深的情况下,查询效率会急剧下降。对于这种情况,我们需要做一些分页查询的优化。
准备
创建脚本
CREATE TABLE student (
id INT NOT NULL AUTO_INCREMENT COMMENT 'ID',
student_number VARCHAR(10) NOT NULL COMMENT '学号',
name VARCHAR(50) NOT NULL COMMENT '姓名',
score DECIMAL(10,2) NOT NULL COMMENT '分数',
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
remark TEXT COMMENT '备注',
PRIMARY KEY (id)
) COMMENT='学生信息表';
执行存储过程
我们通过存储过程的方式往student表中插入500万条数据
-- 创建存储过程
DELIMITER $$
CREATE PROCEDURE insert_student_data()
BEGIN
DECLARE i INT DEFAULT 1;
WHILE (i <= 5000000) DO
INSERT INTO student (student_number, name, score)
VALUES (CONCAT('S', LPAD(i, 7, '0')), CONCAT('张三', i), ROUND(RAND() * 100, 2));
SET i = i + 1;
END WHILE;
END$$
DELIMITER ;
-- 执行存储过程
CALL insert_student_data();
SQL优化
假设我们需要查询 学号,姓名,分数并且按照分数倒序分页查询查询,每页20条记录。
# 查询时间1.6s
SELECT student_number,name,score from student ORDER BY score desc limit 0,20;
# 查询时间2.3s
SELECT student_number,name,score from student ORDER BY score desc limit 10000,20
# 查询时间2.5s
SELECT student_number,name,score from student ORDER BY score desc limit 100000,20
# 查询时间3s
SELECT student_number,name,score from student ORDER BY score desc limit 4000000,20;
我们发现查询时间比较慢,并且随着分页的深入查询时候会更加的慢
查询一下执行计划
EXPLAIN SELECT student_number,name,score from student ORDER BY score desc limit 0,20;
发现type是ALL,发现并未走索引,随即我们给score字段加上索引再测试一次
ALTER TABLE student add index index_score(score)
# 查询时间0.005
SELECT student_number,name,score from student ORDER BY score desc limit 0,20;
# 查询时间0.005
SELECT student_number,name,score from student ORDER BY score desc limit 10000,20
# 查询时间2.5s
SELECT student_number,name,score from student ORDER BY score desc limit 100000,20
# 查询时间3s
SELECT student_number,name,score from student ORDER BY score desc limit 4000000,20;
可以看到在分页较潜的情况下执行的效率很高,但是在分页较深的情况下执行的效率还是一样的
查看执行计划
EXPLAIN SELECT student_number,name,score from student ORDER BY score desc limit 0,20;
可以看到在浅分页的情况下是走了索引并且索引的key就是我们刚添加的那个,额外只执行了 Backward-index-scan(8.0新增的,使用倒序索引扫描)
EXPLAIN
SELECT student_number,name,score from student ORDER BY score desc limit 4000000,20
然后在深分页的情况下type是ALL 全表扫描,并且额外执行的Using filesort(排序操作)
。
如果我们强制的让他使用索引查询
# 查询结果27秒
EXPLAIN
SELECT student_number,name,score from student FORCE INDEX (index_score) ORDER BY score desc limit 4000000,20
我们会发现强制使用索引的查询时间比全表扫描还要长,这是因为我们使用非覆盖索引进行查询的时候会有一次回表查询。
覆盖索引优化
既然是因为有回表操作使得查询效率变低,那么我们可以使用覆盖索引,让他查询的时候通过辅助索引就可以查询到所有信息,就不用进行回表操作。
添加索引
ALTER TABLE student add index index_score_name_student_number(score,student_number,name)
我们为student表添加一个联合索引,然后再执行深分页查询
# 查询时间0.9s
SELECT student_number,name,score from student ORDER BY score desc limit 4000000,20
查询时间显著的缩短了
可以看到因为新添加的索引覆盖了我们需要查询的列,所以不需要进行回表查询,直接走索引即可。
但是 如果我们的查询语句中再新增了一列
SELECT student_number,name,score,create_time from student ORDER BY score desc limit 4000000,20
因为 create_time
字段不再我们的联合索引里面,所以它又将进行全表扫描
延迟关联优化
先通过 where 条件提取出主键,在将该表与原数据表关联,通过主键 id 提取数据行,而不是通过原来的二级索引提取数据行
# 查询时间0.8s
SELECT a.student_number,a.name,a.score ,a.create_time from student as a ,
(SELECT id from student ORDER BY score desc limit 4000000,20) as b WHERE a.id=b.id
分析下执行计划
首先执行的是id=2 的这条计划,走的是我们之前定义的index_score这个索引,然后执行的是第二条也就是别名为a的表可以看到它的类型为主键索引,最后执行的是第一条,临时结果表,虽然是全表扫描,但是我们可以得知的是这张表的结果只有20条,因为我们limit的就是20条记录,所以查询也很快。
从执行计划中可以看到我们的临时表走的是全表扫描,所以如果我们子查询的临时表中的结果比较多的话,这种方式就不推荐使用了。
书签方式
根据id,score 大于最小值或者小于最大值进行遍历。
# 查询时间 0.005s
SELECT id,student_number,name,score from student
WHERE id < 90000000 and score <=100.00
ORDER BY score DESC LIMIT 20
查看执行计划
可以看到type为range效率很高
在进行上一页/下一页查询的时候需要前端传递给我们两个参数(第一行/最后一行)的id跟score,然后根据这两个参数再进行条件查询。
缺点是只能进行上一页下一页的查询,不能进行跳页查询。
标签:20,分页,number,查询,score,student,MySQL,排序,name From: https://www.cnblogs.com/loveletters/p/mysql-oderby-query.html