LIMIT offset, size 在大数据偏移量下变慢,是因为MySQL需先排序完整结果集,再逐行扫描丢弃前offset行;推荐用WHERE+上一页末尾值替代OFFSET,如SELECT * FROM t WHERE id > 6800000 ORDER BY id LIMIT 20。

为什么 LIMIT offset, size 在大数据偏移量下会变慢
MySQL 执行 LIMIT 1000000, 20 时,并不是直接跳到第 1000001 行,而是先按 ORDER BY 排序完整结果集,再逐行扫描、丢弃前 1000000 行,最后取 20 行。即使有索引,B+ 树仍需遍历大量节点定位“第 1000001 条”,I/O 和 CPU 开销都集中在无效扫描上。
常见错误现象包括:
- 第 1 页查得快(
LIMIT 0, 20),第 5000 页突然卡住(LIMIT 99980, 20) -
EXPLAIN显示rows高达百万级,但Extra里写着Using filesort或Using index后仍慢 - 加了
WHERE type = ?条件也无明显改善——因为排序和偏移逻辑没变
用 WHERE + 上一页末尾值替代 OFFSET(推荐首选)
本质是把“跳过 N 行”变成“从某个确定值之后取数据”,彻底绕开偏移量扫描。前提是排序字段有索引、且值可比较(如 id、updated_at)。
实操建议:
- 若按主键
id升序分页:上一页最后一条的id是 6800000,则下一页查SELECT * FROM t WHERE id > 6800000 ORDER BY id LIMIT 20 - 若按时间倒序(如
ORDER BY updated_at DESC):上一页最后一条的updated_at是'2026-04-10 15:22:33',则下一页查SELECT * FROM t WHERE updated_at - 必须给排序字段建索引,否则
WHERE条件无法高效过滤 - 注意时间字段重复问题:如果
updated_at不唯一,需叠加主键(如WHERE updated_at )避免漏/重数据
用覆盖索引 + JOIN 减少回表开销
当无法改写为游标式分页(比如要支持任意页码跳转),又必须用 LIMIT offset, size 时,优先让 MySQL 只扫描索引,不查全表。
实操建议:
- 先用覆盖索引查出 ID:
SELECT id FROM t WHERE type = 8 ORDER BY id LIMIT 1000000, 20(快,因只走主键索引) - 再用这些 ID 关联原表:
SELECT a.* FROM t a JOIN (SELECT id FROM t WHERE type = 8 ORDER BY id LIMIT 1000000, 20) b ON a.id = b.id - 比直接
SELECT * FROM t WHERE type = 8 ORDER BY id LIMIT 1000000, 20快几十倍——实测从 37 秒降到 0.3 秒 - 务必避免
SELECT *在子查询中,只选id;JOIN 时确保a.id有索引(主键天然满足)
哪些场景不适合游标分页
游标式(WHERE + 上一页末值)虽快,但对业务逻辑有硬约束,容易踩坑:
- 用户手动输入页码(如“跳转到第 1234 页”):无法提供上一页末值,只能退回到传统
LIMIT或预计算页边界 - 数据实时写入且排序字段非单调(如
score可能反复更新):上一页末值可能被新插入数据“挤掉”,导致翻页漏数据 - 需要统计总页数(
COUNT(*)):游标分页本身不提供总数,COUNT在千万级表上仍是全表扫,考虑用近似值或异步更新缓存 - 复合排序(如
ORDER BY status, created_at DESC):WHERE 条件需同时满足两个字段的边界,SQL 复杂度陡增,易出错
真正难的不是写出快 SQL,而是判断当前分页需求是否允许放弃“任意页跳转”这个看似合理、实则昂贵的假设。


















