MySQL带排序的分页查询优化

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,然后根据这两个参数再进行条件查询。

缺点是只能进行上一页下一页的查询,不能进行跳页查询。

posted @ 2023-04-03 23:41  loveletters  阅读(405)  评论(0编辑  收藏  举报