LIMIT 1000000, 20 需要扫描 100 万行再丢弃,这就是 OFFSET 分页越往后越慢的根本原因。

一、OFFSET 分页的问题

  • 1.1 原理——LIMIT offset, count → 扫描 offset + count 行
  • 1.2 offset=0:扫描 20 行
  • 1.3 offset=1000000:扫描 1000020 行
  • 1.4 为什么不能跳过前面的行——B+Tree 有序但非跳跃

二、方案 1:游标分页(推荐)

  • 2.1 原理——WHERE id > last_id ORDER BY id LIMIT 20
  • 2.2 使用最后一条记录的 ID 作为游标
  • 2.3 优点——每次只扫描 20 行,效率恒定
  • 2.4 缺点——不能跳页
  • 2.5 适用场景——无限滚动、App 端

三、方案 2:ID 范围分页

  • 3.1 先查询 ID 范围——SELECT id FROM order WHERE ... LIMIT 100000, 20
  • 3.2 再查询详情——SELECT * FROM order WHERE id IN (...)
  • 3.3 优化点——覆盖索引 + 延迟关联

四、方案 3:ES 的 search_after

  • 4.1 ES 的深分页问题——from + size 上限 10000
  • 4.2 search_after 替代 from
  • 4.3 原理——类似于游标分页

五、方案 4:预计算总页数

  • 5.1 不要每次查询 COUNT——用 Redis 缓存
  • 5.2 近似计数——HyperLogLog

六、选型建议

  • 管理后台需要跳页 → ID 范围分页
  • App/Feed 流无限滚动 → 游标分页
  • 搜索场景 → ES search_after

七、总结

  • OFFSET 分页是反模式,应尽量避免大 offset
  • 游标分页是最佳实践