当前位置:首页 > 文章列表 > 数据库 > MySQL > MySQL 大表分页怎样避免深分页扫描

MySQL 大表分页怎样避免深分页扫描

来源:17golang原创 2026-10-23 04:30:54 0浏览 收藏

我在给订单列表加“跳到很后面一页”时,最先看到的不是返回结果变少,而是查询开始为前面的页付费:LIMIT 200000, 20 仍然要找到并跳过前 200000 行。MySQL 官方手册也提醒,LIMIT 配合无法由索引满足的排序时,可能要先选出并排序大量匹配行。

直接做法是把页码分页改成游标分页(keyset pagination):用 created_at 和唯一的 id 组成稳定排序,把上一页最后一条记录作为游标,下一页用范围条件继续读,并让联合索引匹配过滤和排序。

官方地址:https://dev.mysql.com/doc/refman/8.4/en/limit-optimization.html

大表分页的关键不是把 offset 写得更聪明,而是让数据库从“第几页”改为“从哪个已知位置继续读取”。如果业务必须跳到任意页,就保留传统分页;如果主要是下一页、加载更多或按时间浏览,优先考虑 keyset pagination。

消息是什么:深分页慢在要丢弃前面的行

传统写法通常是下面这样:

-- 这是页码分页:offset 越大,数据库需要处理的前置记录越多
SELECT id, created_at, total_amount
FROM orders
WHERE user_id = 42
ORDER BY created_at DESC, id DESC
LIMIT 200000, 20;

即使最终只返回 20 行,offset 也描述了需要越过多少行。若排序键与过滤条件没有合适的索引,MySQL 还可能进入额外排序;若查询需要回表,前面被丢弃的候选行也会增加读取成本。这里的“慢”不是固定由某一个参数造成,而是扫描、排序、回表和丢弃叠加后的结果。

MySQL 深分页查询中条件、排序、扫描、丢弃和回表的静态结构示意图
图1:深分页查询的静态结构示意图,展示 offset 较大时需要处理的扫描与丢弃关系,不是运行截图或性能实测。

适用场景:先确认你真的不需要任意页跳转

我会先看接口的交互方式。消息流、订单时间线、审计记录和后台“加载更多”通常只需要拿到当前页之后的下一页,这类场景非常适合游标分页。客户端保存一个不透明游标,服务端从游标恢复排序键即可。

但报表用户可能会输入页码 50 并要求精确跳转,或者产品必须显示总页数。这时 keyset pagination 不能直接替代所有能力:可以把列表浏览改成游标方式,同时把统计总数、筛选和跳页设计成独立接口,不要为了保留页码而让主列表一直承受深 offset。

快速试用:用稳定排序键定位下一页

先把排序写完整。时间戳可能重复,所以只按 created_at 排序不够;加入唯一的 id 后,每一行都能在排序中得到稳定位置。

-- 第一页只负责建立游标,排序顺序必须和后续查询完全一致
SELECT id, created_at, total_amount
FROM orders
WHERE user_id = 42
ORDER BY created_at DESC, id DESC
LIMIT 20;

假设这一页最后一行是 created_at = '2026-10-22 18:30:00'、id = 98120,下一页使用“更早的时间,或同一时间但 id 更小”的条件:

-- 游标由上一页最后一行产生;DESC 排序对应小于比较
SELECT id, created_at, total_amount
FROM orders
WHERE user_id = 42
  AND (
    created_at 

这条语句表达的是“从上一页末尾之后继续取”,而不是“先找到第 200001 行再丢掉前面所有行”。游标可以在接口层编码成 Base64 或签名字符串,但编码只是传输形式,服务端仍应校验字段格式、用户筛选条件和排序版本。

和旧方案对比:索引必须同时照顾过滤和顺序

查询改成范围条件后,还要让索引路径与它匹配。一个常见起点是把等值过滤列放在前面,再放联合排序列:

-- 索引顺序服务于 user_id 过滤,再服务于时间和主键排序
CREATE INDEX idx_orders_user_created_id
ON orders (user_id, created_at, id);

实际是否使用这个索引仍由优化器结合数据分布和查询列决定,不能只看索引名字。可以用 EXPLAIN 观察访问类型、候选行数和是否出现额外排序;不要把一次执行计划当成所有数据规模下的保证。

方式定位依据适合场景主要代价
LIMIT offset, size页号与偏移量必须跳到任意页、页数较浅深页要处理并丢弃前置行
keyset pagination上一页末尾排序键下一页、加载更多、时间线不能天然跳到任意页,游标要绑定筛选条件

两种方案不是互斥的。我的经验是:搜索结果页保留浅页的 offset,持续向后浏览的主列表使用游标;不要为了“接口统一”让所有入口都采用同一套分页方式。

MySQL keyset pagination 中时间、主键联合排序和下一页游标条件的静态关系图
图2:keyset pagination 的联合排序键与下一页范围条件示意图,重点是稳定边界和索引匹配,不是运行截图。

采用风险:倒序、重复时间和筛选变化要先定规则

倒序要反过来比较。上面的示例是 DESC,因此下一页用小于号;若改为 ASC,就要使用大于号。不要只改 ORDER BY 而忘记范围条件。

游标必须和查询条件绑定。用户切换 user_id、状态、时间范围或排序方向后,旧游标就不应继续使用。服务端可以把筛选摘要、排序版本和最后一行键一起签名,条件不一致时返回重新开始。

新增数据会改变“页码”的含义。keyset 的优势是继续从确定边界读取,不承诺传统页码在实时数据下永远不变。如果业务需要导出一份稳定快照,应增加明确的时间上界或快照标识,而不是把游标分页伪装成静态页码。

末页用数量判断。请求 size + 1 行,返回前 size 行并根据是否多出一行设置 has_more。这样不需要先执行昂贵的总数统计,也能告诉客户端是否继续加载。

-- 多取一行只用于判断是否还有下一页,应用层返回时丢弃这一行
SELECT id, created_at, total_amount
FROM orders
WHERE user_id = 42
  AND (created_at, id) 

行构造比较能让 SQL 更短,但团队应结合目标 MySQL 版本、索引和执行计划确认表达方式;如果需要把边界写得更直白,可以继续使用前面的 OR 条件。关键不在语法长短,而在排序键、比较方向和联合索引三者一致。

我的结论:先改列表语义,再改 SQL

避免 MySQL 深分页扫描的落点有三件事:把排序变成“时间 + 唯一键”的稳定顺序,为过滤和排序建立匹配索引,用上一页末尾值替代大 offset。改造前后都应针对真实数据量查看 EXPLAIN,并分别观察浅页、后页、筛选变化和并发写入下的行为。

如果产品强依赖“跳到第 N 页”,keyset 不是万能替换;如果产品主要是下一页和加载更多,它通常更贴近用户真正的浏览方式。先确认这个边界,再决定是否迁移,往往比单纯给原查询加一个索引更可靠。

深分页是不是一定要改成游标分页?

不一定。页数浅、数据量小或必须任意页跳转时,传统分页更直接;只有后页成本明显且交互允许“从当前位置继续”时,游标分页才更合适。

只给 created_at 建索引可以吗?

如果 created_at 重复,排序边界可能不稳定。通常应加入唯一键作为第二排序列,并让索引顺序与过滤列、排序列共同匹配。

为什么改了 SQL 还是出现 filesort?

可能是过滤条件、排序顺序、表达式或数据分布使优化器选择了其他路径。用 EXPLAIN 分别看 key、rows 和 Extra,再确认索引是否真的覆盖了查询形状。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
MySQL EXPLAIN 怎样分析 UPDATE 或 DELETE 的执行计划MySQL EXPLAIN 怎样分析 UPDATE 或 DELETE 的执行计划
上一篇
MySQL EXPLAIN 怎样分析 UPDATE 或 DELETE 的执行计划
MySQL 时间范围查询为什么没有用上索引
下一篇
MySQL 时间范围查询为什么没有用上索引
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    600次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    641次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    673次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    639次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    460次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码