首页 > 数据库 >MySQL带排序的分页查询优化

MySQL带排序的分页查询优化

时间:2023-04-03 23:57:59浏览次数:44  
标签:20 分页 number 查询 score student MySQL 排序 name

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

相关文章

  • 如何理解MySQL的MVCC多版本并发控制
    前言我们知道在mysql中存在四种隔离级别(读未提交、读已提交、可重复读、序列化),它默认的就是隔离级别就是可重复读,它能够解决脏读、不可重复读问题,并且在innodb引擎下能部分解决幻读问题。在mysqlinnodb存储引擎下RC(读已提交),RR(可重复读)基于MVCC(多版本并发控制)进行并发事务控......
  • MYSQL基础知识之DQL语句
    1、DQL概念DQL英文全称是DataQueryLanguage(数据查询语言),用来查询数据库中的表的记录2、基本查询语法:#查询全部字段SELECT*FROM表名; #查询多个字段SELECT字段1,字段2,字段3...FROM表名; #去重 语法:SELECTDISTINCT字段列表FROM表名;   3、条......
  • Mysql基本语句
    创建用户三种方式:CREATEUSERusername1IDENTIFIEDBY'password';CREATEUSER'username1'@'localhost'IDENTIFIEDBY'password';在MySQL中,可以使用password()函数获取密码的哈希值,查看test1哈希值的SQL语句和执行过程如下:mysql>SELECTpassword(�......
  • MySQL(九)InnoDB行格式
    InnoDB行格式查看默认行格式:select@@innodb_default_row_format;查看数据库表使用的行格式mysql>useatguigudb;ReadingtableinformationforcompletionoftableandcolumnnamesYoucanturnoffthisfeaturetogetaquickerstartupwith-ADatabasechanged......
  • MySQL(九)InnoDB数据结构
    InnoDB数据结构1数据库的存储结构:页​ 索引信息和数据记录都是保存在文件上的,确切来说是保存在页结构中;另一方面,索引是在存储引擎上实现的,MySQL服务器上的存储引擎负责对表中数据的读取和写入工作。不同的存储引擎的存放格式是不同的,比如Memory甚至不使用磁盘进行存储数据。......
  • MySQL(十)表空间结构:区、段与碎片区
    表空间结构:区、段与碎片区为什么要有区?​ B+树中的每一层的页都会形成一个双向链表,双向链表之间的物理位置可能会离得非常远,当遇到范围查询的适用场景的时候,就会定位到最左边和最右边的记录,然后沿着双向链表一直扫描,而如果这其中的两个页面物理上离得特别远,就会成为随机I/O,由于......
  • MYSQL基础知识之DDL语句
    一、DDL概念DDL(DataDefinitionLanguage)语言:数据定义语言,用来定义数据库对象,如数据库、数据表和数据字段,主要是进行定义/改变表的结构、数据类型、表之间的链接等操作。常用的语句关键字有CREATE、DROP、ALTER等。 二、数据库操作2.1、linux环境连接数据库语法:mysql-u用......
  • Mysql主从复制
    工作原理图:主从复制的原理:分为同步复制和异步复制,实际复制架构中大部分为异步复制。复制的基本过程如下:1).Slave上面的IO进程连接上Master,并请求从指定日志文件的指定位置(或者从最开始的日志)之后的日志内容;2).Master接收到来自Slave的IO进程的请求后,通过负责复制的IO进程根据请......
  • mysql数据库优化大全
    数据库优化sql语句优化索引优化加缓存读写分离分区分布式数据库(垂直切分)水平切分MyISAM和InnoDB的区别:1.InnoDB支持事务,MyISAM不支持,对于InnoDB每一条SQL语言都默认封装成事务,自动提交,这样会影响速度,所以最好把多条SQL语言放在begin和commit之间,组成一个事务;2......
  • MySQL实战45讲 笔记
    笔记不要小看一条update语句,在生产机上使用不当可能会导致业务停滞,甚至崩溃。当我们要执行update语句的时候,确保where条件中带上了索引列,并且在测试机确认该语句是否走的是索引扫描,防止因为扫描全表,而对表中的所有记录加上锁。我们可以打开MySQL里的sql_safe_updates参数......