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

来源:这里教程网 时间:2026-02-28 20:51:13 作者:

为什么 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
做游标会导致漏数据或重复。

安全写法:
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
这种逻辑。游标分页改造成本低,效果立竿见影,但前提是排序字段能锚定唯一位置——这点容易被忽略,也最难补救。

相关推荐