mysql如何处理大量数据的分页查询_mysql分页性能调优

1次阅读

offset越大查询越慢,因mysql需扫描跳过前offset行;应改用游标分页,以排序字段值为条件+索引范围扫描,避免全表扫描。

mysql如何处理大量数据的分页查询_mysql分页性能调优

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

MySQL 的 LIMIT offset, size 在底层需要先扫描并跳过前 offset 行,再取 size 行。当 offset 达到几十万甚至百万级时,即使有索引,MySQL 仍需逐行计数、判断是否满足偏移条件,I/O 和 CPU 开销陡增。这不是“查得慢”,而是“不得不扫那么多行”。

  • 执行 EXPLAIN 会看到 rows 值接近 offset + size,而非仅 size
  • 覆盖索引失效:若 select * 或非索引字段参与查询,会导致回表,进一步放大延迟
  • 主从延迟敏感:大 offset 查询常在从库长时间运行,拖慢复制位点

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

核心思路是不依赖行号,改用上一页最后一条记录的排序字段值作为下一页起点。要求排序字段严格唯一且有索引(如自增 id 或带唯一约束的 created_at, id 组合)。

  • 第一页:SELECT * FROM orders WHERE status = 'paid' ORDER BY id ASC LIMIT 50
  • 第二页(假设上页最大 id 是 12345):SELECT * FROM orders WHERE status = 'paid' AND id > 12345 ORDER BY id ASC LIMIT 50
  • 必须去掉 OFFSET,WHERE 条件走索引范围扫描,执行计划中 rowssize
  • 注意方向一致性:升序分页用 >,降序用 ;混合排序(如 <code>ORDER BY created_at DESC, id DESC)需同时比较两个字段

复合排序字段没唯一性?加 id 补足

常见场景是按时间分页:ORDER BY created_at DESC,但同一秒可能有多条记录,直接用 created_at 做游标会导致漏数据或重复。

  • 安全写法:ORDER BY created_at DESC, id DESC,游标条件写成 WHERE created_at
  • 索引必须包含全部排序字段:INDEX(created_at, id),顺序与 ORDER BY 一致
  • 避免在 WHERE 中对排序字段做函数操作(如 date(created_at)),否则索引失效

物理删除不现实时,慎用 SQL_CALC_FOUND_ROWS

它曾被用来查总条数以渲染分页控件,但实际会强制 MySQL 扫全表/全索引,性能比带 OFFSET 还差。现代应用基本应弃用。

  • 替代方案一:前端只显示“下一页”,不显示总页数(如 Twitter、github
  • 替代方案二:用近似值——查 SELECT count(*) 改为 SELECT TABLE_ROWS FROM information_schema.TABLES(误差可接受,尤其对大表)
  • 替代方案三:业务允许时,在写入侧维护统计缓存(如 redis 中的 orders:count),异步更新

真实线上分页卡顿,八成不是 SQL 写错了,而是还在用 LIMIT 1000000, 20 这种逻辑。游标分页改造成本低,效果立竿见影,但前提是排序字段能锚定唯一位置——这点容易被忽略,也最难补救。

text=ZqhQzanResources