导读:本期聚焦于叶子创作的《MySQL大数据量分页查询很慢怎么办?深度分页优化方案详解》,敬请观看详情。LIMIT 100000,10 这类语句为什么越翻页越慢?根源在于MySQL会扫描并丢弃前面10万行记录,再返回后面的10条,偏移量越大扫描行数越多,响应时间也随之飙升。本文从执行原理入手,先用EXPLAIN分析LIMIT分页的扫描行为,再给出几种主流优化手段:利用覆盖索引配合延迟关联减少回表、基于ID定位的游标式分页、书签记录上次位置避免大偏移,以及业务层限制跳页等方案,同时对比各方案的适用场景与优缺点,帮你把深分页查询从数秒降到毫秒级。

分页功能几乎存在于所有后台管理系统和C端列表页中,最常见的写法就是LIMIT offset, size。数据量小的时候一切正常,可当表里的数据涨到几百万甚至上千万行,运营反馈“翻到第几千页就转圈”时,问题就暴露出来了。这篇文章围绕MySQL深度分页慢的根本原因展开,给出几种经过生产验证的优化方案,并对比它们的适用条件。

MySQL大数据量分页查询很慢怎么办?深度分页优化方案详解

一、为什么LIMIT越往后再翻越慢

要理解深度分页的性能问题,得先看MySQL处理LIMIT 1000000, 10时到底做了什么。它并不是直接跳到第100万行开始取数据,而是老老实实从头开始扫描,把前100万零10行都找出来,然后丢弃前100万行,只把最后10行返回给客户端。也就是说,实际扫描的行数和偏移量成正比,偏移量每翻一倍,扫描量也跟着翻倍。

如果查询列上没有合适的索引,情况会更糟。MySQL只能走全表扫描,一百万行的偏移量意味着至少一百万行的磁盘IO和CPU消耗。即使走了二级索引,如果查询的列不全在索引里,每一行还要回表到主键索引取完整数据,而这些回表操作大部分是白费的,因为对应行最后会被丢弃。

用EXPLAIN可以直观验证这一点:

EXPLAIN SELECT id, name, created_at 
FROM orders 
ORDER BY id 
LIMIT 1000000, 10;
-- rows列会显示接近1000010,type为ALL或index
-- 说明优化器预计要扫描约一百万行才能返回10条数据

扫描行数巨大、大量无效回表,这两点叠加起来就是深度分页变慢的核心原因。优化的思路也就顺着这两条展开:要么减少扫描行数,要么避免无效回表。

二、延迟关联:覆盖索引加JOIN的经典方案

延迟关联(Deferred Join)是解决深度分页最常用的手段。它的核心想法是先用一个只查主键的子查询把目标行的ID定位出来,由于子查询只需要访问索引,不涉及回表,扫描速度极快;拿到10个ID之后再和原表JOIN,此时回表只发生10次。

-- 原始写法:扫描1000010行且每行都回表
SELECT id, order_no, user_id, amount, created_at 
FROM orders 
ORDER BY created_at 
LIMIT 1000000, 10;

-- 延迟关联写法:子查询只扫索引,回表仅10次
SELECT o.id, o.order_no, o.user_id, o.amount, o.created_at 
FROM orders o
INNER JOIN (
    SELECT id 
    FROM orders 
    ORDER BY created_at 
    LIMIT 1000000, 10
) t ON o.id = t.id;

要让子查询走覆盖索引,需要保证ORDER BY的列和id都在同一个索引里,比如建一个idx_created_at(created_at),二级索引的叶子节点天然包含主键值,正好满足要求。子查询在索引上顺序扫描定位到第100万行附近,速度远快于回表一百万次。

这个方案的优势是不需要改动业务代码结构,SQL改一改就能上线,兼容跳页场景。但它并不能完全消除扫描成本,子查询依然要在索引上跳过前100万条记录,只是跳得快了很多。在亿级数据量下,翻到极端靠后的页依然会有可感知的延迟,这时就要考虑下面更彻底的方案。

三、游标分页:彻底告别大偏移量

如果业务允许调整交互方式,游标分页(也叫书签分页、Keyset Pagination)是效率最高的方案。它不再传页码,而是把上一页最后一条记录的排序值作为游标传入,下一页查询直接从这个位置往后取:

-- 第一页
SELECT id, order_no, amount, created_at 
FROM orders 
ORDER BY created_at DESC 
LIMIT 10;

-- 后续页:客户端记住上一页最后的created_at和id
SELECT id, order_no, amount, created_at 
FROM orders 
WHERE created_at < '2024-01-15 10:30:00'
   OR (created_at = '2024-01-15 10:30:00' AND id < 10086)
ORDER BY created_at DESC, id DESC 
LIMIT 10;

这里的条件写法有个细节要注意:如果排序字段不唯一,只用created_at做游标会丢数据或重复数据,所以必须加上id作为第二排序条件,组合成一个唯一的定位点。对应的索引建议建成idx_created_id(created_at, id),让WHERE条件和ORDER BY都能走索引,整个查询只需扫描10行。

游标分页的性能与页码深度完全无关,无论第一页还是第一亿条之后的一页,耗时都是稳定的毫秒级。它的代价是牺牲了随机跳页能力——用户只能上一页下一页地翻,不能直接跳到第500页。对于信息流、消息列表这类天然按时间顺序浏览的场景,这个限制几乎无感;但对后台报表这种需要精确跳页的场景,就需要配合其他方案了。

四、业务层优化与方案选型建议

除了SQL层面的改造,很多深分页问题其实可以在业务设计阶段就规避掉。常见做法包括:限制最大可翻页码,比如搜索引擎普遍只展示前几十页结果;把导出类需求改成异步任务,用游标批量拉取而不是分页;对中间页提供近似跳转,落地后再用ID修正。这些手段看似简单,却往往是性价比最高的。

选型时可以参考下面的对比:

方案查询性能是否支持跳页改造成本
直接LIMIT分页随偏移量线性变差支持
延迟关联明显提升,极端深处仍偏慢支持低,只改SQL
游标分页恒定毫秒级不支持中,需改接口协议
限制跳页加异步导出从源头消除问题受限中,需业务配合

实践中比较稳妥的组合是:普通后台列表用延迟关联兜底,保证任意页码可用;面向C端的时间线类列表用游标分页追求极致体验;导出和批量同步场景一律走异步游标任务。上线前记得用EXPLAIN确认执行计划,重点看rows估算值和Extra列是否出现Using index,避免优化了个寂寞。深度分页本身没有银弹,理解扫描和回表的代价来源,再结合业务形态选择方案,才能真正把这类查询稳定压在毫秒级。

MySQL深度分页分页优化慢查询优化修改时间:2026-09-13 07:02:29

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/20260913/55839.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。