MySQL 深分页优化

在实际应用场景中,列表分页查询是很常见的。假设现在存在某张表,已知 ID 是主键,针对 user_name 建立了二级索引。

针对该表进行分页查询。

select * from table order by id limit offset, size;

那么同样都是获取 10 条数据,查询第一页和查询第一百页的速度一样吗?

首先先回忆下 MySQL 查询语句的执行过程:MySQL 内部可分为 server 层和存储引擎层,一条查询语句需要依次经过 连接器 -> 查询缓存 -> 解析器 & 预处理器 -> 分析器 -> 优化器 -> 执行器,执行器通过调用存储引擎层提供的接口,将一行行的数据取出,当这些数据完全符合要求(即满足查询条件),则会放到结果集中,最后再返回给客户端。

MySQL 索引查询可分为主键索引查询和非主键索引查询,其中 InnoDB 存储引擎默认采用 B+ 树索引结构:如果是主键索引,叶子节点存放完整的行数据信息,非叶子节点仅存放索引;如果是非主键索引,叶子节点存放的是主键值,若想获得完整的行数据信息,则还需要根据主键值再查询一次主键索引,即回表查询。

但不管是主键索引还是非主键索引,它们的叶子节点数据都是有序的。以主键索引为例:这些数据根据主键 ID 大小,从小到大进行排序。

基于主键索引的 limit 执行过程

select * from table order by id limit 0, 10;
select * from table order by id limit 6000000, 10;

针对第一条语句,server 层会调用 InnoDB 接口,在 InnoDB 的主键索引中获取到第 0 ~ 10 条完整的行数据,一次性返回给 server 层,并放到 server 层的结果集中,最后返回给客户端。

而在第二条语句中,offset 设置为 6000000,则会在 InnoDB 的主键索引中获取第 0 ~ 6000000 + 10 条完整的行数据,返回给 server 层后根据 offset 的数值进行丢弃,只保留后面的 size 条数据,放到 server 层的结果集中,最后返回给客户端。

可以看到,当 offset 非 0 时,server 层会从引擎层获取到很多无用的数据,而获取这些数据本身也是耗时的。

此外,当进行 select * 查询时,需要拷贝完整的行数据,而拷贝完整数据和只拷贝行数据里的部份列字段的耗时是不一样的。更何况前面 offset 条数据最后都是不要的,所以即使将完整的行数据都拷贝了也没有意义。

修改优化如下。

select * from table where id >= (select id from table order by id limit 6000000, 1) order by id limit 10;

修改后的语句,先执行子查询,从主键索引中获取到 6000001 条数据,然后 server 层丢弃前 6000000 条,只保留最后一条数据的 ID。因为只会拷贝数据行内的 ID 列,而不是拷贝数据行的所有列,所以即使数据量较大,性能还是能有显著提升。

根据子查询获取到的 ID,InnoDB 再走一次主键索引,通过 B+ 树快速定位到 id=6000000 的行数据,然后再向后取 10 条数据。

基于非主键索引的 limit 执行过程

select * from table order by user_name limit 0, 10;

上述查询语句中,server 层会调用 InnoDB 接口,在 InnoDB 的主键索引中获取到第 0 条数据对应的主键 ID 后,再回表查询对应的完整行数据,然后再返回给 server 层,server 层将其放到结果集中,最后返回给客户端。

当 offset 非 0 时,也是会丢弃前面的 offset 条数据。非主键索引的 limit 过程,比主键索引的 limit 过程,多了一步回表查询的耗时。而当 offset 数值特别大的时候,server 层的优化器可能会因为要进行大量的回表操作,从而选择全表扫描。

修改优化如下。

select * from table t1, (select id from table order by user_name limit 6000000, 10) t2 where t1.id = t2.id;

先走非主键索引取出 ID,因为只取主键值,所以不需要回表,性能会稍微快些。在返回 server 层后,同样也是丢弃 offset 条数据,只保留最后的 10 个 ID,然后再用这 10 个 ID 去和 t1 表做 ID 匹配,此时走的是主键索引,将匹配到的 10 条行数据返回,这样就绕开了之前大量的回表操作。

深分页问题

但是上面的两种优化方式,始终无法解决丢弃大量数据的问题,不管是使用 MySQL 还是借助 ES,都只能减缓问题的严重性。

针对需要获取全表数据的使用场景,可以考虑采用分批处理,将当前批次的最大 ID 作为下次筛选的条件。

伪代码如下。

start_id := 0
for {datas := [select * from table where id > start_id order by id limit 100]if len(datas) == 0 {break}handler(datas)start_id = get_max_id_from(datas)
}

针对面向用户的分页展示,可以考虑限制搜索页数范围或者限制间隔页数查询或者直接采用上下页查询的方式。

总结

  • limit offset, sizelimit size 慢,且 offset 的数值越大,SQL 的执行速度越慢
  • 当 offset 过大时,会出现深分页问题,只能通过限制查询数量或者分批查询的方式来缓解
  • 处理深分页问题,更多的应当从实际业务操作角度出发,合理地规避

参考资料

  • https://mp.weixin.qq.com/s/i6FL1iRECiWZ1CCf_juxQQ

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

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

相关文章

Linux CentOS7 wc命令

wc命令的功能为统计指定文件中的字节数、字数、行数, 并将统计结果显示输出。 录入 man wc 可以查看相关信息 基本语法: wc [选项] 文件… 说明:该命令统计给定文件中的字节数、字数、行数。如果没有给出文件名,则从标准输入读取。wc同时也…

C语言进阶第三课-----------指针的进阶----------后续版

作者前言 🎂 ✨✨✨✨✨✨🍧🍧🍧🍧🍧🍧🍧🎂 ​🎂 作者介绍: 🎂🎂 🎂 🎉🎉&#x1f389…

docker总结

Docker实用篇 0.学习目标 1.初识Docker 1.1.什么是Docker 微服务虽然具备各种各样的优势,但服务的拆分通用给部署带来了很大的麻烦。 分布式系统中,依赖的组件非常多,不同组件之间部署时往往会产生一些冲突。在数百上千台服务中重复部署…

ddtrace 系列篇之 dd-trace-java 项目编译

dd-trace-java 是 Datadog 开源的 java APM 框架,本文主要讲解如何编译 dd-trace-java 项目。 环境准备 JDK 编译环境(三个都要:jdk8\jdk11\jdk17) Gradle 8 Maven 3.9 (需要 15G 以上的存储空间存放依赖) Git >2 (低于会出现一想不到的异常&#xf…

大语言模型之十-Byte Pair Encoding

Tokenizer 诸如GPT-3/4以及LlaMA/LlaMA2大语言模型都采用了token的作为模型的输入输出,其输入是文本,然后将文本转为token(正整数),然后从一串token(对应于文本)预测下一个token。 进入OpenAI官…

[2023.09.21]:源码已上传,供大家了解Rust Yew的前后端开发

这个资源是Rust的源代码压缩包,供大家了解Rust Yew的前后端开发。 资源中的代码非常简洁易懂,虽然离商用场景还有一段距离,但是涵盖了前端的组件搭建、事件通信和反向代理,以及后端的Restful API的路由、功能实现和数据库访问。此…

Learn Prompt-Prompt 高级技巧:MetaGPT

MetaGPT是一项引起广泛关注的研究成果,它引入了一个将人工工作流程与多智能体协作无缝集成的框架。通过将标准化操作(SOP) 程序编码为提示,MetaGPT确保解决问题时采用结构化方法,从而减少出错的可能性。 &#x1f389…

【深度学习】ONNX模型快速部署【入门】

【深度学习】ONNX模型快速部署【入门】 提示:博主取舍了很多大佬的博文并亲测有效,分享笔记邀大家共同学习讨论 文章目录 【深度学习】ONNX模型快速部署【入门】前言搭建打包环境打包可执行文件总结 前言 之前的内容已经尽可能简单、详细的介绍CPU【Pytorch2ONNX】和GPU【Pyto…

蓝桥杯打卡Day12

文章目录 接龙数列冶炼金属 一、接龙数列OJ链接 本题思路:本题是一道经典的dp问题,设第i个数的首位数字是first, 末位数字是last。因为第i个数只可能加到一个以first结尾的接龙数列中使得这个接龙数列长度加1并且结尾数字变成last.所以状态转移方程为d…

设计模式_解释器模式

解释器模式 案例 角色 1 解释器基类 (BaseInterpreter) 2 具体解释器1 2 3... (Interperter 1 2 3 ) 3 内容 (Context) 4 用户 (user) 流程 (上下文) ---- 传…

Redis 面试题——缓存穿透、缓存击穿和缓存雪崩

目录 1.缓存穿透2.缓存击穿3.缓存雪崩4.总结 参考文章: 缓存实战(1)缓存雪崩、缓存击穿和缓存穿透入门简介及解决方案 1.缓存穿透 (1)问题描述:缓存穿透是指在高并发场景下,大量的请求访问一个…

23种设计模式汇总详解

设计原则 中文名称英文名称含义解释单一职责原则Single Responsibility Principle(SRP)任何一个软件模块都应该只对某一类行为者负责一个类只干一件事,实现类要单一开闭原则Open-Close Principle(OCP)软件实体(类、模块、函数等)应该是可以扩…

使用scss简化媒体查询

在进行媒体查询的编写的时候,我们可以利用scss与与编译器,通过include混入的方式对代码进行简化,从而大大提高了代码的可维护性,也减少了代码的编写量,废话不多说,直接上代码: // 断点列表 相当…

ChatGpt介绍和国产ChatGpt对比

1.ChatGPT是美国OpenAI研发的聊天机器人程序,2022年11月30日发布。ChatGPT是人工智能技术驱动的自然语言处理工具,它能够通过理解和学习人类的语言来进行对话。 2.ChatGPT是一种基于自然语言处理的聊天机器人程序。它使用深度学习技术,通过对…

JLBANK-IRS统计报表相关的两个存储过程

1、 债项评级统计报表 CREATE OR REPLACE PROCEDURE SP_DEBT_RATING_RESULT_QUERY( P_RATING_TIME VARCHAR2, P_ORGSEQ VARCHAR2, P_SMALL_CORP_LOAN_CD VARCHAR2, P_CUR OUT IRS_REF.T_CURSOR) AS V_ORGSEQ VARCHAR2(12) : …

【Linux】常用工具(下)

Linux常用工具 一、Linux 项目自动化构建工具 - make/Makefile1. 依赖关系和依赖方法2. 伪目标3. make/Makefile 具有依赖性的推导能力(语法扩展)4. 编写一个进度条代码(1)缓冲区(2)\n 和 \r(3&…

《C和指针》笔记29:数组名和指针

看下面的代码 int b[10];b[4]的类型是整型,但b的类型又是什么?它所表示的又是什么?一个合乎逻辑的答案是它表示整个数组,但事实并非如此。在C中,在几乎所有使用数组名的表达式中,数组名的值是一个指针常量…

Flask+pyecharts+SQLAlchemy,统计图的数据存放在mysql中,综合版

ISEE小语 有人问:“世上最廉价的东西是什么?” 在网上看到这样一个回答说: “大概就是付出吧,一贫如洗的真心、一事无成的温柔、一厢情愿的等待。” 回顾上篇 此篇是在【Flask+pyecharts结合,html统计图呈现在前端页面】和【Flask+pyecharts结合,优化前端加导航栏显示】的…

Vue实现大文件分片上传、断点续传

前言 实现大文件分片上传的断点续传以及上传进度条是一个在前端开发中常见且具有挑战性的问题。本篇博客将介绍如何使用Vue框架来实现这个功能,并给出代码示例。 概述 大文件分片上传指的是将一个大文件切割成多个小文件(或称为分片)&…

软件测试-BUG

软件测试-BUG 1.如何合理创建一个BUG 创建bug的要素: 软件的版本发现问题的环境发现问题的步骤预期结果实际结果 Bug报告: 软件版本:Google Chrome浏览器(具体版本号) 发现问题环境:在Windows 10操作系统…