
MySQL 中用 LIMIT offset, size 做分页,当 offset 很大(比如几百万)时,查询会明显变慢——因为 MySQL 仍需扫描并跳过前面所有 offset 行,即使只返回几条数据。这不是 PHP 的问题,而是 SQL 执行逻辑导致的性能瓶颈。优化核心是避免深度偏移扫描。
用游标分页(Cursor-based Pagination)替代 OFFSET
适合按时间、ID 等单调字段排序的场景(如“最新文章”列表)。不再依赖行号,而是记住上一页最后一条记录的排序字段值,下一页查询直接从该值之后开始。
- ✅ 优势:每次查询都走索引范围扫描,复杂度稳定为 O(log n),不受总数据量影响
- ❌ 限制:要求排序字段唯一且无空值;不支持“跳转到第 100 页”这类随机访问
- 示例(按
id降序):
第一页:SELECT * FROM posts ORDER BY id DESC LIMIT 20
第二页(假设上页最大 id 是 12345):SELECT * FROM posts WHERE id
延迟关联(Deferred Join)减少回表开销
当表字段多、有大文本或频繁 JOIN 时,LIMIT 后仍要读取整行数据,I/O 成本高。可先用主键快速定位,再二次查详情。
- 把分页逻辑拆成两步:先查 ID 列表,再用
IN或 JOIN 获取完整数据 - 示例:
SELECT id FROM posts WHERE status = 1 ORDER BY created_at DESC LIMIT 20000, 20
→ 取出 20 个id后,再执行:SELECT * FROM posts WHERE id IN (123, 456, ...) - 注意:IN 列表不宜过长(一般 ≤ 1000),否则可用临时表或 JOIN 优化
预估总数 + 合理限制翻页深度
用户极少真正翻到第 1000 页,但 COUNT(*) 全表统计本身也慢。可结合业务做妥协:
立即学习“PHP免费学习笔记(深入)”;
- 用
EXPLAIN中的rows近似估算总数(误差可接受时) - 对搜索结果,用
COUNT(*)加缓存(如 Redis 存 5 分钟),或仅显示“约 20,000+ 条结果” - 前端禁用“跳转页码”输入框,只提供“下一页/上一页”按钮,并在
offset > 10000时自动切换为游标模式
索引必须覆盖排序和过滤条件
无论哪种分页方式,没有合适索引,一切优化都无效。关键检查点:
- 组合索引顺序应匹配
ORDER BY + WHERE字段,例如:WHERE status = ? ORDER BY created_at DESC→ 建索引(status, created_at) - 若排序字段含 NULL,加
NOT NULL约束或在查询中显式排除(WHERE created_at IS NOT NULL) - 用
EXPLAIN验证type是range或index,且Extra不含Using filesort或Using temporary











