因为MySQL执行LIMIT offset, size时需线性扫描并丢弃前offset行,导致I/O和CPU开销随offset增大而剧增;应改用游标分页(如WHERE id > ? ORDER BY id LIMIT 20)或延迟关联优化。

为什么 OFFSET 越大,分页越慢?
因为 MySQL 在执行 LIMIT offset, size 时,即使只返回 20 行,也会先扫描并跳过前 offset 行——如果 offset 是 100 万,它就得定位到第 1000001 条记录,中间所有行都得经过索引树遍历或回表。这不是“取数据慢”,是“找起点”慢。
常见现象:SELECT * FROM orders ORDER BY created_at DESC LIMIT 999980, 20 耗时数秒甚至超时,而 LIMIT 0, 20 几乎瞬时返回。
- 全表扫描或无覆盖索引时,性能恶化更剧烈
-
ORDER BY字段没索引?直接触发 filesort + 全量排序,OFFSET只是雪上加霜 - InnoDB 的聚簇索引特性让大 offset 定位成本呈近似线性增长
用游标分页(cursor-based pagination)替代 OFFSET
核心思路:不依赖“第 N 页”,改用“从某条记录之后取下 N 条”。前提是排序字段唯一且有索引(推荐用主键或带唯一约束的时间戳+ID组合)。
例如按 id 降序分页:
SELECT * FROM users WHERE id < 100500 ORDER BY id DESC LIMIT 20;
下次请求带上本次结果的最小 id(即 99980),再查:
解析 MySQL 8.0 的 DDL 脚本,并将表结构导出为 Excel 格式。适用于用户需将 MySQL 的 `CREATE TABLE` 语句转换为结构化的 Excel 文件场景,导出内容包含字段名、数据类型、长度及精度等信息。支持多张表的批量处理,每张表对应 Excel 中的一个独立工作表。适用于数据库文档编写、数据字典生成及模式分析等任务。
SELECT * FROM users WHERE id < 99980 ORDER BY id DESC LIMIT 20;
- 必须确保
WHERE条件和ORDER BY字段一致,否则可能漏数据或重复 - 避免用
created_at单独做游标——存在时间相同的情况,需补上id:“WHERE (created_at, id) ” - 首次请求不能用游标,仍需
LIMIT 20;但后续全部走范围查询,速度恒定
强制走覆盖索引 + 延迟关联优化 OFFSET 查询
当无法改造为游标分页(比如前端强依赖页码)、又必须支持跳转任意页时,可用“先查主键,再关联取全字段”减少回表和传输开销。
原始低效写法:
SELECT * FROM products ORDER BY price ASC LIMIT 100000, 20;
优化后:
SELECT p.* FROM products AS p INNER JOIN ( SELECT id FROM products ORDER BY price ASC LIMIT 100000, 20 ) AS tmp ON p.id = tmp.id ORDER BY p.price ASC;
- 子查询只走索引(假设
price和id都在联合索引里),体积小、定位快 - 外层
JOIN按主键回表,比全字段排序 + 大 offset 快得多 - 注意:该技巧对
WHERE条件复杂、无法走索引的场景效果有限
哪些情况仍要小心游标分页?
游标不是银弹。数据实时写入频繁时,游标分页会出现“幻读式跳变”——新插入的记录可能挤在当前游标位置之前,导致用户刷新页面看到重复或遗漏条目。
- 社交 Feed 类场景:新动态持续追加,用
created_at DESC, id DESC游标基本可接受(用户不苛求绝对顺序一致性) - 后台管理类场景:若要求“翻页内容严格静态”,仍需配合应用层缓存或快照(如生成临时分页视图)
- 千万级表 + 高并发游标请求:注意
WHERE ... ORDER BY ... LIMIT的索引选择率,避免优化器误选低效索引
真正难的不是写出游标 SQL,而是判断业务能否容忍“基于最后一条已见数据”的语义——这往往比技术方案本身更需要和产品对齐。

















