分页查询的性能陷阱:深分页
LIMIT 20 很快,LIMIT 1000000, 20 很慢。数据量上去之后,分页往往是第一个暴露的性能问题。
为什么深分页慢
SELECT * FROM orders ORDER BY id LIMIT 1000000, 20;
MySQL 需要真的取出前 1000020 行(多数还要回表),然后丢掉前一百万行。偏移量越大,浪费越多。这不是优化器的问题,是 LIMIT 语义本身。
解法一:游标分页(推荐)
不用偏移量,用「上一页最后一条的排序值」做条件:
-- 第一页
SELECT * FROM orders ORDER BY id DESC LIMIT 20;
-- 下一页:拿上一页最后一条的 id
SELECT * FROM orders WHERE id < 12345 ORDER BY id DESC LIMIT 20;
每一页都是索引范围扫描,代价恒定。移动端无限滚动的列表天然适配这种模式。代价是只能顺序翻页,不能直接跳到第 1000 页——多数产品场景这反而更合理。
排序值不唯一时(如按时间),补一个 id 作为次序键:
WHERE (created_at, id) < ('2026-07-08 10:00:00', 12345)
ORDER BY created_at DESC, id DESC
解法二:延迟关联
必须支持跳页时,先用覆盖索引取出主键,再回表取整行:
SELECT o.* FROM orders o
JOIN (SELECT id FROM orders ORDER BY id LIMIT 1000000, 20) t
ON o.id = t.id;
子查询只扫描索引(不回表),比原句快一个数量级。再配合只 SELECT 需要的列,效果更好。
解法三:业务侧限制
多数场景用户根本不会翻到 500 页。搜索产品普遍限制最大偏移(Google 也一样)。如果业务允许,直接限制 LIMIT 10000 并提示缩小范围,是最便宜的方案。
附带两个坑
COUNT(*)全量统计在千万级表上也很慢,分页总数可以估数(显示「1000+」)或用缓存;- ORDER BY 的列必须有索引,否则每页都是 filesort。混合排序(如按热度)考虑预计算排序分。
一句话:能改交互就改交互,用游标分页;不能改就延迟关联。LIMIT 100000, 20 这种写法上线前就该被 Code Review 拦下。