mysql如何使用索引优化分页查询_mysql分页性能分析

5次阅读

offset越大查询越慢,因为mysql需真实扫描并丢弃前n行;应避免深度分页,改用游标分页或覆盖索引+主键关联优化。

mysql如何使用索引优化分页查询_mysql分页性能分析

为什么 OFFSET 越大,LIMIT 查询越慢

MySQL 的 OFFSET 不是跳过已扫描的行,而是真实地扫描并丢弃前 N 行。比如 select * FROM orders ORDER BY id LIMIT 10000, 20,MySQL 会先按 id 排序,再逐行读取前 10020 行,只返回后 20 行——前 10000 行全白读了,还占 I/O 和 CPU。

如果没走索引或排序字段无索引,还会触发 using filesort,性能雪上加霜。

  • ORDER BY 字段必须有索引,且和 WHERE 条件能共用(最左前缀原则)
  • 避免 SELECT *,只查必要字段,减少回表开销
  • 不要用 OFFSET 做“深度翻页”,10 万行之后基本不可控

用游标分页(Cursor-based Pagination)替代 OFFSET

核心思路:不依赖行号,改用上一页最后一条记录的排序键值作为查询起点。例如按 created_at DESC, id DESC 分页,第二页就从上一页末尾的 (created_at, id) 值继续查:

SELECT * FROM posts  WHERE (created_at, id) < ('2024-05-01 10:20:30', 12345) ORDER BY created_at DESC, id DESC  LIMIT 20;

这个查询能命中联合索引 (created_at, id),全程走索引范围扫描,不依赖偏移量。

  • 必须保证排序字段组合唯一(或加主键兜底),否则可能漏数据或重复
  • 不能跳页(如直接跳到第 100 页),但对“下一页”场景极友好
  • 前端需保存上一页末尾的游标值,而不是页码

覆盖索引 + 主键关联优化传统 LIMIT

当必须用页码(比如后台管理列表),可先用覆盖索引快速定位主键,再回表取数据:

SELECT p.* FROM posts p INNER JOIN (   SELECT id FROM posts    WHERE status = 1    ORDER BY created_at DESC, id DESC    LIMIT 10000, 20 ) AS tmp ON p.id = tmp.id;

子查询只查 id,若 status 和排序字段都在同一索引里(如 (status, created_at, id)),就能完全走索引,不回表;外层再用主键关联取完整数据,大幅减少扫描量。

  • 索引要包含 WHERE 字段 + ORDER BY 字段 + 主键(用于覆盖)
  • 子查询的 LIMIT 仍存在深度偏移问题,但只作用于轻量级主键列,代价小很多
  • 注意 MySQL 8.0+ 对这种写法优化更好,5.7 下需确认执行计划是否真用了索引

哪些索引设计会让分页失效

常见“假索引”陷阱:

  • INDEX(created_at) 单独存在,但查询带 WHERE user_id = 123 → 无法同时满足过滤与排序,要么走 user_id 索引然后 filesort,要么走 created_at 索引但全表扫 user_id
  • INDEX(user_id, created_at) 有,但排序是 ORDER BY created_at DESC, id DESCid 不在索引中,仍需回表+额外排序
  • 使用函数或表达式排序,如 ORDER BY date(created_at) → 索引失效,必然 filesort

真正有效的索引得匹配整个查询模式:WHERE 条件最左前缀 + ORDER BY 字段顺序 + 包含主键或所需查询列(覆盖)。

深度分页不是加个索引就能解决的事,关键在把“跳过 N 行”的思维,换成“从某条记录往后取”。游标分页看着麻烦,却是唯一能线性扩展的方式。

text=ZqhQzanResources