MySQL 深分页优化:limit 100000, 10 慢在哪,怎么救

线上出过一次事故:订单列表页翻到后面越来越卡,运营反馈翻到第几千页时,一个请求要跑十几秒。

DBA 查看慢查询日志,SQL 本身语法没问题:

复制代码
SELECT * FROM orders ORDER BY id LIMIT 100000, 10;

没有语法错误,但查询就是很慢。这就是典型的深分页问题。

今天把这件事从头到尾捋一遍:慢的根源是什么,常见解法优势在哪、又有哪些失效场景,以及我最终落地的方案。

一、先搞清楚它到底慢在哪

很多人对深分页的理解只有一句话:offset 越大,扫描的行越多。这句话没错,但只说对了一半。

MySQL 执行 LIMIT 100000, 10 的真实过程:沿着索引读出前 100010 行,丢弃前面 100000 行,只返回最后的 10 行。

如果这条 SQL 走的是二级索引(例如按 create_time 排序),前 10 万行每读取一行都要回表:拿主键去聚簇索引查询完整行数据,每一次回表都是一次随机 IO。

也就是说,深分页的损耗是双重的:无效扫描 + 无效回表 。

这也能解释一个常见现象:同样偏移 10 万行,按主键排序的分页,比按二级索引排序快很多。因为主键索引本身就是数据,不需要回表。

只记住 "扫描行数多",你只能想到有限的解法;把回表开销也纳入考量,优化思路才会更开阔。

二、游标法:速度快,但有边界

最经典的优化方案就是游标分页:不再使用 offset,以上一页最后一条记录的 ID 作为查询起点。

复制代码
-- 示例
SELECT * FROM orders WHERE id > #{lastId} ORDER BY id LIMIT 10;

主键索引可以直接定位起点,只扫描 10 行,offset 带来的大量扫描、回表开销全部消失。实测千万级单表场景,原本几秒的查询可以压到几毫秒。

但方案评审时,我经常会提醒它的局限性:游标法的适用条件远比写法苛刻。

  1. 排序字段不是主键,比如按 create_time 排序:可以用时间做游标,但时间会重复,需要组合 (create_time, id) 联合游标;
  2. 业务需要跳页:无法拿到 "上一页最后一条 ID",游标直接失效。运营后台直接跳转到第 500 页这类需求,游标法不支持;
  3. 筛选条件动态多变:十几个可选查询维度,很难为每一种组合建立联合索引;
  4. 主键是雪花 ID / UUID:ID 不保证连续有序,id > ? 的查询语义本身存在隐患。

游标法非常适合 C 端 "下一页、顺序浏览" 的场景,性能上限很高,但它不是万能方案。

三、延迟关联:绝大多数场景的最优解

游标无法使用的时候,我的首选方案是延迟关联。核心思路:把查询主键和查询完整数据拆成两步。

子查询只在索引上查询 id,如果提前建好 (create_time, id) 这类联合覆盖索引,子查询只走索引,全程不需要回表。哪怕扫描 10 万行,只是顺序读取索引,开销很低。拿到 10 条主键之后,再统一回表 10 次获取全部字段。

示例 SQL:

复制代码
SELECT o.* FROM orders o
INNER JOIN (
    SELECT id FROM orders ORDER BY create_time LIMIT 100000, 10
) t ON o.id = t.id;

对比原始写法:同样扫描 10 万行,回表次数从 10 万次下降到 10 次,性能是量级上的提升。

延迟关联优势:

  • 不需要改动前端交互,支持跳页;
  • 兼容动态筛选条件;
  • 仅在 SQL 和索引层面改造,业务侵入性小。

它不是速度最快的方案,但通用性最好。大部分深分页问题,做到延迟关联 + 覆盖索引就足够解决。

四、选型:不是所有深分页都值得优化

最后聊一个容易被忽略的点:优化的前提是值得优化。

  • 百万以内表:原生 limit 基本够用,不要过度设计;
  • 千万级单表、C 端高频顺序翻页:优先游标分页,性能最好;
  • 多条件筛选、需要支持跳页:延迟关联 + 覆盖索引;
  • 数据量更大、查询维度极其复杂:再考虑同步数据到 Elasticsearch、ClickHouse 等专用查询引擎做分页。

这里要权衡成本:数据同步延迟、一致性维护、额外组件运维成本。单纯为了解决分页接口引入 ES,很多时候属于杀鸡用牛刀。

还有一个反直觉的结论:后台管理系统低频查询,比如一个月才翻几百页,慢 2 秒其实影响很小。资源优先投入到 C 端列表这种高 QPS 的核心链路。

回到开头那次线上事故:

C 端订单列表改成游标分页,管理后台复杂筛选页面使用延迟关联。两周上线后,慢查询日志里再也没有出现这条慢 SQL。

深分页优化没有标准答案,只有场景和边界。

把慢的原因拆成扫描开销、回表开销两层,理清每个方案的适用边界,远比死记 "用 ID 游标代替 limit" 更有价值。

相关推荐
Francek Chen22 分钟前
【大数据处理与分析】数据仓库Hive:09 Hive 编程实践
大数据·数据仓库·hive·hadoop·sql
麦壳饼28 分钟前
SonnetDB 5.0 正式发布:增强 SQL 与事务,开放九模型 MCP 读写
数据库·人工智能·sql·sonnetdb
深蓝AI3 小时前
Gemini 4 Carbon 登顶却买不到:59 分拿下智能指数第一,单任务 7.76 美元,幻觉率从 4% 涨到 11%
人工智能·程序员
怕浪猫9 小时前
一个人就是一支工程队:AI 时代超级个体的 90 天变现 SOP
程序员
Decay__10 小时前
18_MySQL 智能巡检方案 —— 巡检项清单 / 一键巡检脚本 / 健康评分
运维·数据库·sql·mysql·架构
java_logo10 小时前
Docker 部署 Liquibase:轻松搭建数据库变更管理
数据库·docker·容器·数据库管理·liquibase·轩辕镜像·数据库变更
凤凰院凶涛QAQ11 小时前
《MySQL内核拆解:从SQL到InnoDB》(四) : 联合查询 & 视图
数据库·sql·mysql
码农小河6611 小时前
基于SSM+JSP的高校专业信息管理系统【附源码+数据库】
java·开发语言·数据库
张小姐的猫11 小时前
【MySQL】访问 & 数据流动原理
android·数据库·mysql·adb·1024程序员节