深分页怎么导致索引失效了?提供6种优化的方案!

news2024/12/27 12:21:35

深分页怎么导致索引失效了?提供6种优化的方案!

上篇文章说到索引失效的几种规则,其中就有包括 深分页回表太多导致索引失效 的场景

本篇文章来聊聊深分页场景中的问题并提供几种优化方案,以下是本篇文章的思维导图:

image-20240128150009803.png

深分页问题

那么什么是深分页问题呢?

在MySQL的limit中:limit 100,10MySQL会根据查询条件去存储引擎层找到前110条记录,然后在server层丢弃前100条记录取最后10条

这样先扫描完再丢弃的记录相当于白找,深分页问题指的就是这种场景(当limit的偏移量过大时会导致性能开销)

-- 0.04s
select SQL_NO_CACHE * from student where age = 18 limit 10;
-- 4.049s
select SQL_NO_CACHE * from student where age = 18 limit 5000,10;

该表中存在二级索引:idx_age_name 是学生年龄age和学生名称name的联合索引(该二级索引上只存在字段age,name,id)

这条SQL会基于MySQL最优成本选择使用 idx_age_name 或者 聚簇索引

假设它使用二级索引 idx_age_name,我们来分析一下使用二级索引的流程:

  1. 它会先根据二级索引定位到第一条满足age=18的记录

  2. 由于二级索引上的记录没有完整字段,因此会回表查询聚簇索引获取完整字段

  3. 将结果返回给server层,并根据这条记录的next找到下一条记录

循环1-3的过程,在二级索引上找到满足查询条件age=18的前5010条记录(或者直到不满足age=18),然后舍弃前5000条,取最后10条

image-20240128120002319.png

在这个过程中:先查二级索引接着回表获取完整记录然后返回给server层再查下一条记录

由于二级索引是联合索引,当age相等时,主键id不一定是有序的,这样回表就会产生随机IO

当深分页场景使用二级索引时会涉及回表(随机IO),如果偏移量太大回表的数据量也会很大,MySQL认为成本太大不偏向使用二级索引从而导致索引失效

那么该如何优化深分页这样的问题呢?从这里分析可以得到从两个方面进行优化:

  1. 让它不要回表,避免回表的开销
  2. 让它不要舍弃前XX条记录,避免白查询

接下来结合不同的方法进行讨论

覆盖索引避免回表

当业务上允许时可以使用覆盖索引避免回表,查完二级索引就交给server层再去查下一条记录

-- 4.049s
select SQL_NO_CACHE * 
from student 
where age = 18 limit 5000,10;

-- 0.034s
select SQL_NO_CACHE id,age,student_name 
from student 
where age = 18 limit 5000,10;

虽然说覆盖索引依旧会舍弃前XX条记录,但没有回表的开销已经快了不少

但如果业务上不允许还是要查较多在聚簇索引上的字段,又或者偏移量还是太大的情况,我们还是需要使用其他的方案

游标分页

为了避免limit中的偏移量,可以自己来存储该偏移量

我们可以使用上次查询的最大值来当作这次的查询条件(游标分页)

--  12.899s
select * from seat 
where seat_code = 'caicaiseat' 
limit 99990,10; -- 最后一条记录 seat_id = 988380

select * from seat 
where seat_code = 'caicaiseat' limit 100000,10;

--  0.189s  满足查询条件情况下主键有序 可以使用上一次记录
select * from seat 
where seat_code = 'caicaiseat' and seat_id > 上次查询最大记录 
limit 10;

select * from seat 
where seat_code = 'caicaiseat' and seat_id > 988380 
limit 10;

其中limit 100000,10seat_id > 988380 limit 10 查询结果相同,但是这种做法是有前提条件的

前提条件

  1. 需要一个列来记录上一次查询的最大值(通常是主键,后面就直接用主键表达,大家明白就好),并且满足查询条件时主键需要是有序的
  2. 因为本次查询需要依赖上一次查询的主键最大值,因此分页查询只能是连续的,不能进行跳页(比如查完第一页直接查第一百页)

在上面案例SQL中会使用二级索引 idx_seat_code (seat_code,seat_id),当使用二级索引时,由于seat_code是等值查询,满足条件时id是有序的

如果是原来的SQL使用这种做法会导致查询出来的结果不一致,因为在二级索引上当age = 18时主键不一定有序

select SQL_NO_CACHE * from student 
where age = 18 and id > 6726705  
limit 10;

乱序该如何解决呢?当然是排序呀!

select SQL_NO_CACHE * from student 
where age = 18 and id > 上次查询最大记录 
order by id 
limit 10;

但是排序又会带来新的问题:可能更偏向使用聚簇索引(全表扫描),如果使用二级索引还需要对id排序(临时表),具体还要查看执行计划分析

游标分页排序下的SQL和原始limitSQL结果是不同的,因为原始的id无序,但它们都满足(业务)查询条件age=18,只是做分页时顺序不同

-- 原始limit SQL
select SQL_NO_CACHE * from student 
where age = 18 
limit 5000,10;

-- 游标分页
select SQL_NO_CACHE * from student 
where age = 18 and id > 上次查询最大记录 
order by id 
limit 10;

使用游标分页时需要使用主键记录每次查询的最大值,并且需要满足查询条件后主键值是有序的,只能在连续分页的场景使用,不能跳页,比如滑动分页(一边滑动一边分页)

子查询定位

另一种避免limit 偏移量太大的方式是通过子查询定位到第一条记录

子查询也是类似于游标分页,定位第一条记录就相当于先找到偏移量

select * from seat 
where seat_code = 'caicaiseat' 
limit 100000,10;

-- 0.068s 通过二级索引先定位到主键值
select * from seat 
where seat_code = 'caicaiseat'
and seat_id >= 
    (select seat_id from seat 
    where seat_code = 'caicaiseat' 
    limit 100000,1) 
limit 10;

使用子查询 select seat_id from seat where seat_code = 'caicaiseat' limit 100000,1 定位到第一条记录的主键值

然后再通过 seat_id >= 定位到的第一条记录 limit 10 查出需要的10条记录

子查询定位的方案也有使用前提:

  1. 子查询可以使用二级索引快速定位(不用回表)

  2. 满足查询条件后主键需要有序(因为使用 seat_id >=

子查询定位支持跳页,但需要使用二级索引定位且满足条件后主键值有序

in + 子查询

在游标分页与子查询使用二级索引定位的场景中总是需要记录偏移量的列(主键)有序,遇到无序的场景还需要排序,增加性能开销

有没有更好的办法避免排序呢?

id >= X 需要主键有序,但是 id in (x,x...) 似乎就不需要主键有序了呀

使用子查询常用的搭配in,因为分页时子查询数据量也不大,可以使用in来进行查询

select SQL_NO_CACHE * from student 
where age = 18 
and id in 
    (select id from student 
    where age = 18 
    limit 5000,10);

但是MySQL好像不支持in与limit同时使用,这样使用会报错

1235 - This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery'

于是只能把子查询的结果封装成临时表

select SQL_NO_CACHE * 
from student 
where age = 18 
and id in 
    (select id 
    from (select id 
          from student 
          where age = 18 
          limit 5000,10) 
    tmp);

in + 子查询的方案即支持跳页又不用排序,虽然会生成临时表但数据量较少

联表查询 + 子查询

熟悉MySQL中in优化(半连接)的同学,一定能够知道in与内连接的奇妙关系

在某些符合条件的场景下,in会被优化为内连接

(感兴趣或者不熟悉的同学可以看这篇文章MySQL半连接优化)

当然我们也可以手动编写内连接的联表查询来让其进行关联

-- 4.049s 原始
select SQL_NO_CACHE * 
from student 
where age = 18 
limit 5000,10;

-- 0.033s 联表 + 子查询
select SQL_NO_CACHE * 
from student s 
inner join (select id tmp_id 
            from student 
            where age = 18 
            limit 5000,10) tmp
on s.id = tmp_id

联表查询的思路与in相同,都能够支持跳页和主键无序

需求沟通

其实这几种方案要么实现不回表,要么实现不用偏移量,在解决这类问题时其实要先与需求进行沟通:

1、是否有必要查这么多记录,记录太多时能否提醒用户减少查询范围(比如起止时间)

2、如果不能修改查询条件的情况下且数据量确实大(比如查18岁学生还有很多分页),考虑是否能让用户翻页翻到这么后呢?

...

总结

本文描述发生深分页问题的原因以及各种解决方案,总结如下:

方法描述使用前提优点缺点
覆盖索引通过覆盖索引避免回表,limit还是会放弃前XX条记录查询的列都在二级索引上不用回表,避免随机IO还是会舍弃前XX条记录
游标分页通过主键记录偏移量,避免limit放弃前XX条记录记录主键,满足条件时主键需要有序避免limit放弃前XX条记录不能跳页,如果满足条件时主键无序还需要排序
子查询定位通过使用二级索引子查询快速定位第一条偏移量的记录,避免limit放弃前XX条记录使用二级索引定位,满足条件时主键需要有序与游标分页相比,能够跳页子查询时还是会舍弃前XX条记录,如果满足条件时主键无序还需要排序
in + 子查询使用in关联子查询定位的主键使用二级索引定位,使用临时表支持跳页、主键无序生成临时表,子查询数据量大会影响性能
联表 + 子查询使用内连接关联子查询定位的主键使用二级索引定位,使用临时表支持跳页、主键无序生成临时表,子查询数据量大会影响性能
需求沟通根据具体场景进行沟通防止深分页问题发生产品经理答应省事产品经理没那么容易答应

深分页问题是因为MySQL limit时,会先把记录查询出来,再舍弃前XX条记录所导致的

不同的方案适合不同的业务场景,在收到数据量较大的分页需求时先进行沟通,无法避免时再做优化

如果需要查询的列在二级索引上都存在,可以使用二级索引(覆盖索引)避免回表

如果满足查询条件后主键有序并且业务上不用跳页那么可以选择游标分页

如果满足查询条件后主键有序并且业务上需要支持跳页,可以选择子查询

如果满足查询条件后主键(记录偏移量的列)无序,那么可以选择in或联表的方案

本文由博客一文多发平台 OpenWrite 发布!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1418376.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

go学习之air库的使用

首先下载air库 go install github.com/cosmtrek/air之后你需要去找到库下载的地方,若使用的是go mod可以使用命令 go env GOPATH找到下载库的位置 进入后,有bin,pkg目录,进入bin目录,你能看到air.exe文件 这时候将此…

数据结构(队列Queue)

文章目录 一、队列1、队列的定义2、队列的顺序实现2.1、初始化2.2、入队2.3、出队2.4、查找2.5、判断队列 满/空 3、队列的链式实现3.1、初始化3.2、入队3.3、出队 4、双端队列 一、队列 1、队列的定义 2、队列的顺序实现 2.1、初始化 //初始化 void InitQueue(SqQueue &Q…

微信小程序(二十一)css变量-定义页面主题色

注释很详细,直接上代码 上一篇 新增内容: 1.使用css变量 2.消除按钮白块影响 3.修改图标样式 源码: npmTest.json {"navigationStyle": "custom","usingComponents": {//引入vant组件"van-nav-bar"…

使用py-spy对python程序进行性能诊断学习

py-spy简介 py-spy是一个用Rust编写的轻量级Python分析工具,它能够监视正在运行的Python程序,而不需要修改代码或者重新启动程序。Py-spy可以在不影响程序运行的情况下,采集程序运行时的信息,生成火焰图(flame graph&…

API:低代码平台的强大秘诀与无限可能

应用编程接口 (API) 是应用程序以可编程格式访问其关键能力和功能的一种方式,从而其他应用程序可以利用它们。API 本质上支持应用程序之间的无缝数据流,使开发人员能够在应用程序中添加更多功能,而无需依赖大量编码。 举一个简单的例子。 您…

55-工厂模式创建对象,instanceof检测,自定义构造函数

1.对象的概括JavaScript中所有事物都是对象:字符串,数值,数组,函数。对象就是带有属性和方法的特殊数据类型。当函数被保存为一个对象的属性时,它就可以称为这个对象的方法(例如方法模式调用 this指向调用对象) 2.用字面量/自定义对象的方式创建单个对象 <script>/…

华为三层交换机与防火墙对接配置上网示例

三层交换机与防火墙对接上网配置示例 组网图形 图1 三层交换机与防火墙对接上网组网图 三层交换机简介配置注意事项组网需求配置思路操作步骤配置文件 三层交换机简介 三层交换机是具有路由功能的交换机&#xff0c;由于路由属于OSI模型中第三层网络层的功能&#xff0c;所以…

别再做“背锅侠”!软件测试工程师被开发吐槽,如何应对?

作为一名软件测试工程师&#xff0c;我们的角色可以算是“战场上的后勤”&#xff0c;战役的胜败和所有团队人员都息息相关。但是难免碰到战役失败后&#xff0c;很多团队互相推脱的局面&#xff0c;而测试人员就是所有团队中的弱势群体&#xff0c;自然是首当其冲的背锅侠&…

【基础算法练习】单调队列与单调栈模板

文章目录 单调栈模板题代码模板算法思想 单调队列模板题代码模板算法思想 单调栈 模板题 题目链接&#xff1a;ACwing 830. 单调栈 代码模板 #include <iostream> #include <vector> #include <stack>using namespace std;const int N 100010;vector<…

群辉开启WebDav服务+cpolar内网穿透实现移动端ES文件浏览器远程访问本地NAS文件

文章目录 1. 安装启用WebDAV2. 安装cpolar3. 配置公网访问地址4. 公网测试连接5. 固定连接公网地址6. 使用固定地址测试连接 本文主要介绍如何在群辉中开启WebDav服务&#xff0c;并结合cpolar内网穿透工具生成的公网地址&#xff0c;通过移动客户端ES文件浏览器即可实现移动设…

Centos7 单机单网卡安装 OpenStack

本文实际环境 vmware 虚拟机&#xff1a; 网络采用的桥接方式&#xff0c;和我的物理网络在一个网段 CPU开启虚拟化 虚拟机安装系统后&#xff0c;配置上静态IP&#xff0c;能连接外网就行了&#xff0c;最好是把内核升级到5.19以上 1、初始化准备 1&#xff09;关闭防火墙 …

有趣的css - 第一个字符串自动生成文字图标

在设计 app 界面的时候&#xff0c;要展示一部分最新的资讯入口&#xff0c;然后出了一张下面的 UI 稿。 UI稿截图如下&#xff1a; 列表设计比较简单&#xff0c;就是列表前面的圆形图标这块&#xff0c;我个人觉得还是有点意思的。 一般的话&#xff0c;大概率都是用js限制…

ASP .NET Core Api 使用过滤器

过滤器说明 过滤器与中间件很相似&#xff0c;过滤器&#xff08;Filters&#xff09;可在管道&#xff08;pipeline&#xff09;特定阶段&#xff08;particular stage&#xff09;前后执行操作。可以将过滤器视为拦截器&#xff08;interceptors&#xff09;。 过滤器级别范围…

《SPSS统计学基础与实证研究应用精解》视频讲解:在线分析处理报告

《SPSS统计学基础与实证研究应用精解》5.1 视频讲解 视频为《SPSS统计学基础与实证研究应用精解》张甜 杨维忠著 清华大学出版社 一书的随书赠送视频讲解5.1节内容。本书已正式出版上市&#xff0c;当当、京东、淘宝等平台热销中&#xff0c;搜索书名即可。本书旨在手把手教会使…

RabbitMQ“延时队列“

1.RabbitMQ"延时队列" 延迟队列存储的对象是对应的延迟消息&#xff0c;所谓“延迟消息”是指当消息被发送以后&#xff0c;并不想让消费者立刻拿到消息&#xff0c;而是等待特定时间后&#xff0c;消费者才能拿到这个消息进行消费 注意RabbitMQ并没有延时队列慨念,…

图片中的水印怎么去掉?教你三个去水印方法

在拍摄照片时&#xff0c;有时候会遇到不期而遇的路人出现在镜头中&#xff0c;给照片带来不必要的干扰。这时候我们就需要把路人给去掉&#xff0c;让照片变的更加完美。下面我将给大家分享三个把照片中的路人去掉的小妙招。 一、水印云 水印云是一款非常实用的图片处理工具…

机器学习3-简单线性回归

需求&#xff1a; 现在要根据学生的学习时间来预测学习成绩&#xff0c;给出现有数据&#xff0c;用来训练模型并预测新数据。 分析&#xff1a; 使用线性回归模型。 代码&#xff1a; import pandas as pd import matplotlib.pyplot as plt from sklearn.model_selection i…

Day02-课后练习2(数据类型和运算符)

参考答案博客链接跳转 文章目录 巩固题1、案例&#xff1a;今天是周2&#xff0c;100天以后是周几&#xff1f;2、案例&#xff1a;求三个整数x,y,z中的最大值3、案例&#xff1a;判断今年是否是闰年4、分析如下代码的计算结果5、分析如下代码的计算结果6、分析如下代码的计算…

SpringBoot之JWT登录

JWT JSON Web Token&#xff08;JSON Web令牌&#xff09; 是一个开放标准(rfc7519)&#xff0c;它定义了一种紧凑的、自包含的方式&#xff0c;用于在各方之间以JSON对象安全地传输信息。此信息可以验证和信任&#xff0c;因为它是数字签名的。jwt可以使用秘密〈使用HNAC算法…

做人力RPO蓝海项目需要有人力资源工作经验吗?

在当前的商业环境中&#xff0c;人力资源外包服务已经成为了许多企业的选择。其中&#xff0c;人力RPO(招聘流程外包)作为人力资源外包的一种形式&#xff0c;尤其在蓝海项目中备受瞩目。那么&#xff0c;对于想要涉足人力RPO领域的个人或企业来说&#xff0c;是否需要具备丰富…