SQL大表分页优化_深度分页与游标分页

5次阅读

深度分页慢的关键在offset,mysql需扫描并丢弃前n行;推荐游标分页,即用上一页最后排序字段值(如id>1058)替代offset,并配合复合索引、覆盖索引、延迟关联等优化。

SQL大表分页优化_深度分页与游标分页

深度分页为什么慢?关键在 OFFSET

当执行 select * FROM orders ORDER BY id LIMIT 100000, 20 时,mysql 仍需扫描前 100000 行才能跳到目标位置。数据量越大,OFFSET 越大,性能越差——不是因为取 20 条慢,而是因为“扔掉”那 10 万行代价太高。

用主键/索引列替代 OFFSET:游标分页(推荐)

核心思路是:不依赖行号偏移,而记住上一页最后一条记录的排序字段值,下一页从该值之后开始查。

  • 第一页:SELECT * FROM orders WHERE status = 1 ORDER BY id ASC LIMIT 20
  • 第二页(假设第一页最大 id 是 1058):SELECT * FROM orders WHERE status = 1 AND id > 1058 ORDER BY id ASC LIMIT 20
  • 必须确保 ORDER BY 字段有索引(如 INDEX(status, id)),否则无法高效定位
  • 游标值(如 id > 1058)要由前端传回,不能用 OFFSET 计算,避免漏数或重复

其他实用优化手段

单靠游标还不够?再加几层保障:

  • 覆盖索引:只查需要的字段,避免回表。例如 SELECT id, order_no, amount FROM orders …,配合 INDEX(status, id, order_no, amount)
  • 延迟关联:大表 JOIN 时,先用游标分页获取主键,再用 IN 关联详情。比如先查出 20 个 id,再 SELECT * FROM orders WHERE id IN (…)
  • 禁止跳页:前端禁用“跳转到第 100 页”按钮,只提供“下一页/上一页”,保证游标链连续
  • 时间范围兜底:对按时间排序的场景(如 created_at),可用 WHERE created_at > ‘2024-01-01’ AND … ORDER BY created_at DESC LIMIT 20,比纯 ID 游标更符合业务直觉

什么情况仍需慎用游标?

游标分页不是银弹:

  • 用户需要随机跳页(如搜索后翻到第 50 页),游标无法直接支持,可结合 ES 或预计算页码映射
  • 排序字段存在大量重复值(如多个订单 created_at 相同),需增加二级排序字段(如 ORDER BY created_at DESC, id DESC)确保唯一性
  • 数据实时写入频繁且排序字段更新多(如 status 频繁变更),可能导致游标查询结果错乱,此时应考虑物化视图或读写分离+一致性快照
text=ZqhQzanResources