当前位置: 首页 > news >正文

MySQL优化:使用 LIMIT 进行分页查询时,偏移量过大造成查询性能降低问题分析

一、原因分析

  1. 数据读取量过大
    随着偏移量不断增大,比如使用 LIMIT offset, limit 语句(offset 表示偏移量,limit 表示每页显示的数量),数据库需要先跳过前面大量的数据行,再去读取指定数量的目标行。例如要查询第10000页(假设每页10条数据),那偏移量就是99990,数据库要先定位到这之前的99990行,即使最终只取后面的10条数据,这个跳过前面数据行的过程开销很大,尤其是数据量极为庞大时,会耗费大量的I/O资源以及内存等。
  2. 索引失效问题
    通常情况下,普通的基于偏移量的分页查询,在执行过程中可能无法有效利用索引来快速定位数据。如果不能合理利用索引,数据库只能通过全表扫描或者按照顺序逐行读取数据的方式来达到指定的偏移位置,这远比通过索引快速定位要慢得多。

二、解决策略

1.使用覆盖索引

尽量让查询所需的列都包含在索引中,这样在查询过程中可以直接从索引里获取数据,减少回表操作(回表指的是先通过索引找到主键,再根据主键去数据表中查找其他列数据的过程)。例如原本查询 SELECT id, name, age FROM users LIMIT 10000, 10,可以创建包含 idnameage 的联合索引,让查询直接从索引中获取这些数据,提升效率。

2.延迟关联或子查询优化

核心思路:
先通过一个子查询快速定位到我们真正想要获取的数据所在的 id 范围(也就是 “关键的 id 段”),然后再通过关联操作获取这些 id 对应的数据行的完整信息。

三、示例

以下是分别使用延迟关联和子查询来优化用户表深度分页的具体示例,并结合实际场景详细说明了优化思路:

1.延迟关联优化示例

用户表users ,表中包含字段 id(主键)、username(用户名)、registration_date(注册日期)、last_login_date(上次登录日期)等,现在需要按照用户的注册日期倒序分页展示用户信息,且要获取从第 5000 条记录开始的 10 条用户记录。

优化前的普通分页查询(性能较差)
SELECT * 
FROM users 
ORDER BY registration_date DESC 
LIMIT 5000, 10;
  • 问题分析
    这样的查询语句在执行时,数据库会先对整个 users 表按照 registration_date 进行排序,然后跳过前面的 5000 条记录,最后返回 10 条记录。当数据量较大时,排序操作以及跳过大量记录的过程会消耗大量的系统资源,导致查询效率低下,尤其是随着偏移量(这里的 5000)越来越大,性能问题会愈发明显。
优化后的延迟关联查询
SELECT u.* 
FROM users u
JOIN (SELECT id FROM users ORDER BY registration_date DESC LIMIT 5000, 10
) AS sub_query ON u.id = sub_query.id;
  • 优化思路及执行过程分析
    • 子查询部分
      • 首先,子查询 (SELECT id FROM users ORDER BY registration_date DESC LIMIT 5000, 10) 的作用是基于 registration_date 字段的排序,快速定位到我们想要获取的那 10 条记录对应的 id 值。因为只查询 id 这一个字段,数据量相对很小,并且如果在 registration_date 字段上建有索引(比如创建了 (registration_date, id) 的复合索引),数据库可以利用索引进行有序扫描,高效地定位到从第 5000 条记录开始的那 10 个 id ,避免了对全表数据的大量读取和处理。
    • 关联部分
      • 接着,通过 JOIN 操作将子查询获取到的 id 集合(以临时表 sub_query 的形式存在)与原 users 表进行关联,关联条件是 u.id = sub_query.id。这样就能根据这些 id 值从原表中获取对应的完整用户记录(通过 u.* 表示获取 u 表中关联上的 id 对应的所有列数据)。相比于优化前需要先对全表排序并跳过大量记录的方式,这种延迟关联的方法直接精准地获取到了目标分页数据,大大减少了不必要的数据读取和处理操作,从而显著提升了查询性能。
2.子查询优化示例

按照用户上次登录日期(last_login_date)倒序分页,获取从第 8000 条记录开始的 15 条用户记录,并且只展示用户名(username)和上次登录日期(last_login_date)这两个字段信息。

优化前的普通分页查询(性能较差)
SELECT username, last_login_date
FROM users 
ORDER BY last_login_date DESC 
LIMIT 8000, 15;
  • 问题分析
    和前面类似,此查询需要先对整个表按照 last_login_date 进行排序,再跳过前面 8000 条记录后取 15 条记录返回,在数据量较大时,这样的操作会导致性能不佳,尤其是偏移量较大的深度分页场景下,效率问题突出,而且这里虽然只查询两个字段,但整个查询过程还是涉及了大量不必要的数据处理步骤。
优化后的子查询查询
SELECT username, last_login_date
FROM users 
WHERE id IN (SELECT id FROM users ORDER BY last_login_date DESC LIMIT 8000, 15
);
  • 优化思路及执行过程分析
    • 子查询部分
      • 子查询 (SELECT id FROM users ORDER BY last_login_date DESC LIMIT 8000, 15) 先基于 last_login_date 的排序,利用相关索引(如 (last_login_date, id) 复合索引)快速定位到从第 8000 条记录开始的 15 个 id 值。由于只获取 id 字段,数据量小且能高效利用索引扫描,避免了全表扫描大量数据,快速得到了符合分页位置的 id 集合。
    • 主查询部分
      • 主查询 SELECT username, last_login_date FROM users WHERE id IN (...) 则通过 IN 操作符,利用前面子查询获取到的 id 集合来筛选出对应的用户记录,只获取我们需要展示的 usernamelast_login_date 这两个字段信息。这样就避免了先对全表进行复杂的排序和跳过大量记录的操作,而是直接根据子查询确定的 id 范围精准获取目标数据,提升了查询的效率,尤其适用于这种只需要部分字段且有大偏移量的分页场景。

总的来说,无论是延迟关联还是子查询优化方式,核心都是通过先高效定位目标数据的 id 范围,再基于此获取完整或部分数据,以此来避开传统大偏移量分页时全表扫描、大量数据跳过等导致性能低下的问题,从而优化深度分页场景下的查询性能。在实际应用中,可以根据具体的业务需求、数据特点以及索引情况等因素来选择更合适的优化方法。

四、延迟关联与子查询优化的对比分析

  • 延迟关联:通过JOIN连接子查询结果(如SELECT t1.* FROM t1 JOIN (子查询) t2 ON t1.id = t2.id
  • 子查询优化:通过IN/EXISTS过滤主查询(如SELECT * FROM t1 WHERE id IN (子查询)
特性延迟关联(JOIN)子查询(IN/EXISTS)
语法结构FROM table JOIN (子查询) ON 关联条件WHERE column IN (子查询)
执行流程1. 执行子查询获取ID集合
2. 通过JOIN关联原表
1. 执行子查询获取ID集合
2. 主查询过滤ID
适用场景大偏移量分页(如LIMIT 100000,20)子查询结果集较小(如IN条件仅包含少量ID)
索引依赖必须有覆盖索引(如(status, create_time, id)主键索引或唯一索引
性能优势减少回表次数,直接通过ID关联数据利用IN操作的高效性过滤数据

对于超大数据量的分页(如LIMIT 100000,20),延迟关联通常比IN更高效,原因如下:

  1. JOIN的执行逻辑更优

    • JOIN会先物化子查询结果(生成临时表),再通过主键关联,适合处理大量ID。
    • IN子查询可能会逐行执行(取决于数据库优化器),当子查询结果集很大时性能下降。
  2. 索引利用更充分

    • 延迟关联通过覆盖索引直接获取ID,避免回表。
    • 子查询的IN若没有合适索引,可能退化为全表扫描。
  3. 临时表优化

    • 现代数据库(如MySQL)对JOIN临时表有专门优化(如内存临时表、并行处理)。
1.适用场景对比
场景推荐优化方式理由
大偏移量的全量字段查询延迟关联通过ID关联原表,避免扫描大量完整数据行
特定条件下的分页子查询+覆盖索引利用索引直接获取ID列表,减少回表次数
超大数据量的分页书签分页记录上次查询的ID边界(如WHERE id > last_id),彻底避免偏移量
频繁排序的分页复合索引确保(WHERE字段, ORDER BY字段)的复合索引存在,加速排序
http://www.xdnf.cn/news/1067203.html

相关文章:

  • AS32A601与ASM1042芯片在电力系统自动化监控中的应用效能分析
  • 基于PostgreSQL的百度或高德等POI多层级分类的数据库设计
  • Towards Generalizable Diabetic Retinopathy Grading in Unseen Domains
  • 【ARM 嵌入式 编译系列 7.5 -- GCC 打印链接脚本各段使用信息】
  • c++IO类
  • HTML语义化标签
  • ubuntu安装postman教程并中文汉化详细教程
  • 互联网大数据求职面试:从Zookeeper到Flink的技术探讨
  • Gateway路径匹配规则易错点
  • 自回归(AR)与掩码(MLM)的核心区别:续写还是补全?
  • VitePress搭建静态博客
  • 【机器人-深度估计】双目深度估计原理解析
  • 【格与代数系统】示例2
  • 黑马程序员苍穹外卖DAY1
  • 【AI News | 20250623】每日AI进展
  • 基于openfeign拦截器RequestInterceptor实现的微服务之间的夹带转发
  • 支持java8的kafka版本
  • Python csv 模块
  • Spring MVC极简入门:从@Request到Postman的全链路开发
  • python中学物理实验模拟:平抛运动和抛物运动
  • 苹果芯片macOS安装版Homebrew(亲测) ,一键安装node、python、vscode等,比绿色软件还干净、无污染
  • 触摸屏(典型 I2C + Input 子系统设备)从设备树解析到触摸事件上报
  • 深入浅出Node.js后端开发
  • Python基础之函数
  • Python基础(​​FAISS​和​​Chroma​)
  • Redis哨兵模式深度解析与实战部署
  • 如何实现财务自由
  • 操作系统 第九章 部分
  • 飞往大厂梦之算法提升-7
  • 第一节 布局与盒模型-Flex与Grid布局对比