SQL 分页查询 LIMIT 高效实现方法

2次阅读

SQL 分页查询 LIMIT 高效实现方法

当数据量大时,直接用 LIMIT offset, size 做分页会越来越慢,尤其 offset 很大(比如 100 万)时,mysql 仍需扫描前 100 万行才能跳到目标位置。真正高效的分页不是靠“跳过”,而是“定位”。

用主键/唯一索引做游标分页(推荐)

适用于按主键(如 id)或时间字段(如 created_at)有序查询的场景。核心思想是:不依赖 offset,而是记录上一页最后一条的主键值,下一页从该值之后开始查。

  • 第一页:select * FROM orders WHERE status = 1 ORDER BY id ASC LIMIT 20
  • 第二页(假设第一页最后 id 是 1056):SELECT * FROM orders WHERE status = 1 AND id > 1056 ORDER BY id ASC LIMIT 20
  • 优势:每次都是范围查询 + 索引覆盖,无需扫描跳过行,响应稳定在毫秒级
  • 注意:ORDER BY 字段必须有索引;不能跳页(如直接翻到第 100 页),但符合多数列表滚动场景

延迟关联优化大 offset 场景

当必须支持任意页码跳转(如后台管理),且主键无法用于游标时,可用“先查 ID,再关联详情”的方式减少回表开销。

  • 原始低效写法:SELECT * FROM users ORDER BY id LIMIT 100000, 20
  • 优化写法:SELECT u.* FROM users u INNER JOIN (SELECT id FROM users ORDER BY id LIMIT 100000, 20) t ON u.id = t.id
  • 原理:子查询只扫描主键索引(轻量),外层再通过主键精准回表,避免全字段扫描前 10 万行
  • 适用:MySQL 5.7+,且表有主键/唯一索引;效果取决于索引宽度和数据分布

避免常见陷阱

  • 不要在无索引字段上 ORDER BY + LIMIT:会导致 filesort,offset 越大越慢
  • 慎用 count(*) 分页总数:大表全表统计极耗资源;可改用估算(如 SHOW table STATUS)、缓存总数、或前端“加载更多”代替总页数展示
  • 注意 NULL 和边界值:若排序字段含 NULL,需明确指定 NULLS FIRST/LAST(MySQL 8.0+ 支持),或在 WHERE 中过滤
  • 复合排序需一致索引:如 ORDER BY category, id,则索引应为 (category, id),否则无法高效利用

其他实用技巧

  • 对高频分页字段(如 status + created_at)建立联合索引,让排序和过滤同时走索引
  • EXPLAIN 检查执行计划,确认 key、rows、Extra(尤其避免 using filesort / Using temporary)
  • 业务允许时,限制最大页码(如只允许查前 1000 页),或改用搜索+筛选替代深度分页
text=ZqhQzanResources