OFFSET越大越慢是因为MySQL需真实扫描并丢弃前N行,无法跳过索引中间段;延迟关联需子查询只查主键且用JOIN+USING;游标分页要求排序字段唯一、用范围条件而非OFFSET、客户端保存上页末尾排序键。

为什么 OFFSET 越大越慢?
MySQL 的 OFFSET 不是跳过前 N 行再取数据,而是让引擎先扫描并丢弃前 N 行——哪怕你只要 10 条,OFFSET 1000000 也得真实执行 1000010 行的主键查找+回表。InnoDB 索引有序,但 LIMIT offset, size 无法利用索引跳过中间段。
- 每次翻页都重复扫描前面所有已跳过的记录
-
EXPLAIN 显示 rows 值随 OFFSET 线性增长,不是常数
- 即使加了复合索引,
ORDER BY created_at DESC LIMIT 10 OFFSET 500000 仍要定位到第 500001 条的索引位置,无法“二分跳转”
延迟关联(Deferred Join)怎么写才生效?
核心思路:用覆盖索引快速定位主键,再二次关联取完整字段。避免在大偏移量下反复回表。
- 必须保证子查询只查主键(或唯一、紧凑的排序字段),且该字段上有有效索引
- 外层
JOIN 必须用 USING 或明确 ON pk = pk,不能写成 WHERE t1.id IN (subquery)(MySQL 会转成物化临时表,失去延迟效果)
- 子查询里不能有
SELECT *、不能引用外层表字段、不能带 GROUP BY 或聚合
SELECT t.* FROM orders t
INNER JOIN (
SELECT id FROM orders
ORDER BY created_at DESC, id DESC
LIMIT 10 OFFSET 100000
) AS tmp USING (id);
EXPLAIN 显示 rows 值随 OFFSET 线性增长,不是常数 ORDER BY created_at DESC LIMIT 10 OFFSET 500000 仍要定位到第 500001 条的索引位置,无法“二分跳转” - 必须保证子查询只查主键(或唯一、紧凑的排序字段),且该字段上有有效索引
- 外层
JOIN必须用USING或明确ON pk = pk,不能写成WHERE t1.id IN (subquery)(MySQL 会转成物化临时表,失去延迟效果) - 子查询里不能有
SELECT *、不能引用外层表字段、不能带GROUP BY或聚合
SELECT t.* FROM orders t
INNER JOIN (
SELECT id FROM orders
ORDER BY created_at DESC, id DESC
LIMIT 10 OFFSET 100000
) AS tmp USING (id);注意:created_at 和 id 要一起建联合索引,否则子查询仍可能全表扫描。
游标分页(Cursor-based Pagination)必须满足哪些条件?
游标分页不是“换种写法”,而是放弃页码概念,改用上一页最后一条的排序值作为下一页起点。它快,但要求严格。
- 排序字段必须有唯一性保障:单字段如
id 可以;created_at 不行(可能重复),得补 id 做二级排序
- 查询必须带
WHERE (sort_col, id) < (last_value, last_id) 这类范围条件,不能用 OFFSET
- 客户端必须保存上一页末尾的完整排序键值,比如
{"created_at": "2024-03-15 10:22:33", "id": 88721}
SELECT * FROM orders
WHERE (created_at, id) < ('2024-03-15 10:22:33', 88721)
ORDER BY created_at DESC, id DESC
LIMIT 10;
id 可以;created_at 不行(可能重复),得补 id 做二级排序 WHERE (sort_col, id) < (last_value, last_id) 这类范围条件,不能用 OFFSET {"created_at": "2024-03-15 10:22:33", "id": 88721} 如果排序字段允许 NULL,WHERE 条件要额外处理,否则漏数据。
什么时候该切游标、什么时候还能扛 OFFSET?
没有银弹。游标分页对“实时滚动加载”友好,但不支持跳转任意页;OFFSET 在小偏移(OFFSET )、缓存命中高、或业务接受弱一致时仍有存在价值。
<ul>
<li>
<code>OFFSET 能用的前提:平均查询响应 < 50ms,且业务能容忍翻到 100 页后变慢
page=5 参数就去算 OFFSET DATE(created_at))、表达式或联表字段,基本无法安全做游标 真正卡住的往往不是技术选型,而是排序依据是否稳定、可比较、无歧义。比如用 updated_at 当游标字段,恰好某条记录被并发更新两次,就可能漏掉或重复某条。










