Yihui’s Blog

MySQL 中如何解决深度分页问题?

日期:2026-07-11
标签:#面试 #八股 #后端 #MySQL #数据库 #场景题

一句话答案

优先使用基于稳定排序键的游标分页,让数据库从上次位置继续扫描;必须随机跳页时可先用覆盖索引定位主键,再回表查询目标行。

面试口语版

LIMIT 1000000,20 仍要扫描并丢弃前一百万行,Offset 越大越慢,而且并发插入会导致重复或遗漏。列表下拉场景使用 Keyset Pagination,例如按 create_time DESC, id DESC 排序,下一页条件写成 (create_time,id) < (?,?),并建立匹配的联合索引。若产品必须跳到任意页,可以让子查询只扫描覆盖索引拿到 20 个主键,再与原表关联,减少大量回表,但前面索引项仍要扫描。

SQL 示例

SELECT * FROM orders
WHERE user_id = ?
  AND (create_time, id) < (?, ?)
ORDER BY create_time DESC, id DESC
LIMIT 20;

关键细节

  • 游标必须包含唯一稳定的排序键,单用时间可能重复。
  • 查询条件、排序方向和联合索引顺序要匹配。
  • 任意跳页和实时变化数据难以同时保证高性能与稳定结果。
  • 超远页报表可异步导出或使用搜索/分析系统。

面试官追问

  1. 游标分页如何处理相同创建时间?
  2. 覆盖索引延迟关联为什么更快?
  3. 游标分页能跳到第 1000 页吗?

面试官追问参考答案

1. 游标分页如何处理相同创建时间?

使用 (create_time,id) 复合排序,id 作为唯一决胜字段;下一页同时携带两者并使用元组比较或等价展开条件。这样排序全序稳定,不会因时间相同重复或漏数据。

2. 覆盖索引延迟关联为什么更快?

子查询只读取较窄的二级索引页,避免对被丢弃的百万行逐条回表;拿到目标 20 个主键后才回表取完整列。它仍有大 Offset 扫描成本,只是显著降低 I/O。

3. 游标分页能跳到第 1000 页吗?

不能直接随机跳转,除非已有该页附近的锚点游标。可缓存分段锚点、限制可跳页范围,或对强随机页需求使用离线快照/搜索引擎;产品上常改为连续翻页。

学习清单

  • 掌握 Keyset Pagination SQL。
  • 理解覆盖索引延迟关联的收益和局限。
维护与整理 · Yihui在 GitHub 上编辑

继续阅读

浏览全部文章