MySQL 大表分页为什么越翻越慢:游标分页、复合索引和上一页游标设计
订单后台一开始只有几千行,翻页接口写成 LIMIT 100000, 20 也不觉得慢。数据涨到几千万后,第一页仍然很快,到了第 5000 页却要等一两秒,慢的不是返回 20 行,而是数据库为了找到这 20 行先处理了前面那一大段记录。
MySQL 大表分页越翻越慢,核心原因是传统 LIMIT offset,size 的写法需要先扫描并丢弃大量偏移位置的无效记录,当偏移量达到几万、几十万级别后,扫描和回表的开销会线性上涨,就算加了普通索引也很难解决根本问题,游标分页、复合索引的组合就是用来绕开这个性能瓶颈的常用方案。
大表分页越往后越慢的核心根源是深OFFSET需要跳过大量无效数据,基于上一页最后一条记录的全排序键做游标、搭配覆盖过滤条件+排序规则的复合索引,就能把分页查询的扫描成本稳定在常量级别。
LIMIT offset,size的深分页会让 MySQL 扫描并跳过大量候选行,页码越深成本越明显。- 游标分页要把过滤列、排序列和唯一键组合成稳定的复合索引,不能只按一个可能重复的时间字段翻页。
- 上一页最后一条记录的排序键是下一页游标;游标必须带上同一排序规则需要的全部字段。
- 数据会变化时,先明确“允许轻微变化”还是“固定快照”,不要把两种语义混在一个接口里。
深分页真正慢在哪里
假设订单列表只看 status = 'paid',按创建时间倒序、订单号倒序排列:
SELECT id, order_no, created_at, amount
FROM orders
WHERE status = 'paid'
ORDER BY created_at DESC, id DESC
LIMIT 100000, 20;
这里的 100000 不是“从磁盘的第 100001 行直接定位”。优化器需要沿着可用访问路径找到满足条件的记录,再把前 100000 个结果丢掉,最后才留下 20 个返回。索引能减少回表和排序,但不能让 OFFSET 本身消失。

先把分页模式说清楚:页码跳转还是连续浏览
游标分页不是所有列表的替代品。需要用户输入第 37 页、跳到最后一页,或者必须展示总页数时,传统页码仍然直观;新闻流、订单流水、审计记录和移动端“加载更多”则通常是连续向后取数据,更适合用上一页末尾的排序键继续查。
| 场景 | 更合适的方式 | 主要代价 |
|---|---|---|
| 后台页码跳转 | LIMIT/OFFSET | 深页扫描成本随 offset 增长 |
| 时间线连续加载 | 游标分页 | 不能天然跳到任意页 |
| 结果必须固定 | 快照或版本号分页 | 需要保存一致性边界 |
复合索引和游标要成对设计
示例接口按状态过滤,再按 created_at、id 倒序排列,可以先准备这样的索引:
CREATE INDEX idx_status_created_id
ON orders (status, created_at, id);
第一列对应等值过滤,后两列对应稳定排序。id 很关键:同一秒可能有很多订单,只用 created_at 会让边界不唯一,下一页可能重复或漏掉记录。
第一页可以正常取:
SELECT id, order_no, created_at, amount
FROM orders
WHERE status = 'paid'
ORDER BY created_at DESC, id DESC
LIMIT 20;
假设这一页最后一行是 created_at = '2026-08-25 02:10:00'、id = 880120,下一页就用这两个值组成游标:
SELECT id, order_no, created_at, amount
FROM orders
WHERE status = 'paid'
AND (created_at

为什么不能只用 created_at 做游标
如果条件只有 created_at ,上一页末尾时间相同的其他订单会被整体跳过。反过来,如果只写 created_at ,边界记录又可能重复出现。这个问题在毫秒精度不足、批量导入和同一事务集中写入时尤其常见。
因此游标要覆盖排序键的完整元组。排序是 created_at DESC, id DESC,比较条件也必须同时表达“时间更早”或“时间相同但 id 更小”。更换排序方向时,比较符号也要一起换,别只改 ORDER BY。
把 EXPLAIN ANALYZE 作为上线前的核对点
不要只看接口响应时间。准备接近生产分布的数据,在 MySQL 上分别执行浅页和深页查询,观察实际读取行数、排序和回表情况:
EXPLAIN ANALYZE
SELECT id, order_no, created_at, amount
FROM orders
WHERE status = 'paid'
ORDER BY created_at DESC, id DESC
LIMIT 20;
再把游标条件替换成真实上一页边界。重点看访问行数是否随页码增长,是否使用了 idx_status_created_id,返回 20 行时是否额外读取了大批无关记录。这里别急着只盯着“用了索引”,索引命中不代表读取量已经合理。
常见反例:排序不稳定、游标失真和数据变化
排序字段重复却没有唯一补充键
同一时间戳下的记录没有确定先后,数据库可以在两次查询中给出不同顺序。把唯一递增键作为最后排序键,才能让边界可复现。
把前端显示页码当成游标
页码是展示概念,不是数据位置。游标应由服务端按排序键生成,前端只负责原样带回;不要让客户端修改时间戳或主键后再提交。
列表跨越了业务快照边界
游标分页强调“继续向前读”,期间新插入的记录可能出现在后续请求之前,已经读过的记录也可能被更新。若导出任务要求整批结果固定,应使用批次版本、业务截止时间或专门快照,而不是指望普通游标自动提供一致性快照。
上线前的判断清单
- 排序是否包含唯一键,游标字段是否覆盖完整排序元组?
- 过滤列和排序列是否对应同一个复合索引,索引顺序是否匹配查询条件?
- 接口是否明确支持下一页,而不是暗中承诺任意页码跳转?
- 新增、更新、删除发生时,产品是否接受列表轻微变化?
- 是否用接近生产规模的数据执行过浅页、深页和空结果边界测试?
相关问题
游标分页还能返回 total 总数吗?
可以单独统计,但 COUNT(*) 可能成为另一条昂贵查询。若总数不是核心交互,通常返回是否还有下一页更划算。
为什么游标需要编码?
编码主要是避免把多个排序字段暴露成可编辑参数,并保持 URL 或请求体格式稳定;它不是安全边界,服务端仍要校验字段类型和查询范围。
MySQL 里可以直接用元组比较吗?
在排序方向一致、列语义明确时可以使用行构造器比较,但上线前要用 EXPLAIN ANALYZE 验证计划。混合 ASC、DESC 或需要兼容多版本时,显式 OR 条件更容易审查。
大表分页的关键不是把 SQL 改成某个固定模板,而是让“过滤、排序、边界”成为一个整体。连续浏览就把上一页最后的完整排序键交给下一页;需要固定结果则另行设计快照边界。两种需求分开,索引和接口都会更容易验收。
GitHub Issues 如何用模板约束缺陷信息:从新建模板到提交后核对
- 上一篇
- GitHub Issues 如何用模板约束缺陷信息:从新建模板到提交后核对
- 下一篇
- GitHub CodeQL 2.26.3 更新了什么:Actions 查询识别与升级注意事项
-
- 数据库 · MySQL | 4小时前 | MySQL · 数据库 · 权限管理 · 故障排查 · 账号安全 · 账号锁定 MySQL 8.4 FAILED_LOGIN_ATTEMPTS PASSWORD_LOCK_TIME ACCOUNT UNLOCK
- MySQL 8.4 账号锁定怎么恢复:FAILED_LOGIN_ATTEMPTS、锁定状态与解锁验收
- 494浏览 收藏
-
- 数据库 · MySQL | 8小时前 |
- MySQL 分区表为什么没有变快:按时间查询的分区裁剪验证方法
- 310浏览 收藏
-
- 数据库 · MySQL | 13小时前 | MySQL · 数据库连接池 · 生产排障 · mysql 连接池 wait_timeout 断线重连
- MySQL 连接池怎么设置 wait_timeout:空闲连接回收与断线重连边界
- 420浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- ljg-skills
- ljg-skills 是李继刚开源的 AI 技能与提示词集合,面向大模型使用者整理了一批可复用的 prompt、角色设定和任务技能模板,适合用于学习提示词设计、搭建个人 AI 工作流和沉淀团队常用智能体能力。
- 5235次使用
-
- MELO音乐
- MELO音乐是一站式AI视频与音乐制作助手,对标suno, udio的高品质体验。提供伴奏生成、原创写词、无损导出、哼唱识曲、混音变声等全套音频与短视频编辑工具。无论是流行Kpop、电音说唱、民谣古风、摇滚儿歌还是商用轻音乐,MELO为你免费谱曲,轻松做同款!
- 4740次使用
-
- UniScribe
- UniScribe 是一款 AI 音视频转文字与内容整理工具,支持上传音频、视频文件或粘贴 YouTube 链接,自动生成转写文本、摘要、思维导图和关键问题,并支持多格式导出,适合会议记录、课程学习、访谈整理和内容创作复盘。
- 4691次使用
-
- 剧云
- 剧云是专业中文剧本创作平台,安全稳定运行十余年,集成AI编剧、剧本医生审核、人物小传、剧情关系图、大纲编写、多人协作、Word导入导出、版权管控功能,数据安全防护,轻松高效创作剧本。
- 4948次使用
-
- 万象有声
- 万象有声,一个专为有声创作者打造的新一代智能有声内容创作平台。平台提供专业的智能拆章、智能画本编辑、AI配音、AI生成音效、后期制作、智能对轨、智能审听等有声创作全流程工具,可以帮助创作者高效、低成本创作出引人入胜的有声作品。立即体验,让有声书制作更简单!
- 4907次使用
-
- MySQL 明明加了索引,为什么查询还是很慢?先查这 6 个点
- 2026-06-27 374浏览
-
- 接口列表页越查越慢怎么办:N+1 查询从 120 次降到 3 次
- 2026-06-29 180浏览
-
- golang MySQL实现对数据库表存储获取操作示例
- 2022-12-22 499浏览
-
- golang 基于 mysql 简单实现分布式读写锁
- 2023-01-07 384浏览
-
- golang cache带索引超时缓存库实战示例
- 2022-12-31 234浏览

