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

为什么 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 条件走索引范围扫描,执行计划中rows≈size - 注意方向一致性:升序分页用
>,降序用;混合排序(如 <code>ORDER BY created_at DESC, id DESC)需同时比较两个字段
复合排序字段没唯一性?加 id 补足
常见场景是按时间分页:ORDER BY created_at DESC,但同一秒可能有多条记录,直接用 created_at 做游标会导致漏数据或重复。
CRMEB Min是CRMEB品牌全新推出的一款轻量级、高性能、前后端分离的开源电商系统,完善的后台权限管理、会员管理、订单管理、产品管理、客服系统、CMS管理、多端管理、页面DIY、数据统计、系统配置、组合数据管理、日志管理、数据库管理,一键开通短信、产品采集、物流查询等接口,系统采用TP6+Mysql+Uniapp+iView+Redis+workerman+form-builder等最流行热
- 安全写法:
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 这种逻辑。游标分页改造成本低,效果立竿见影,但前提是排序字段能锚定唯一位置——这点容易被忽略,也最难补救。










