MySQL大表分页越查越慢优化方案
前言
做网站开发的同学肯定遇到过这种情况:数据库里有一张几百万行甚至上千万行的大表,做分页查询的时候,第一页第二页查得很快,但是翻到第100页、第1000页的时候,查询就变得特别慢,甚至直接卡死。
这是为什么呢?怎么优化才能让大表分页查询不卡呢?这篇文章就详细讲讲MySQL大表分页越查越慢的原因,以及对应的优化方案。
为什么分页会越查越慢
先看一下最常见的分页查询写法:
SELECT * FROM orders ORDER BY id LIMIT 100000, 10;
这个SQL的意思是:从 orders 表里,跳过100000条数据,取后面10条。
很多同学以为这个查询很快就能出来,其实不是的。MySQL执行这个SQL的时候,会先扫描前100010条数据,然后把前面的100000条扔掉,只返回最后10条。
也就是说,你翻到第1000页的时候,MySQL要先扫描100000多条数据,然后再扔掉。翻的页数越深,MySQL要扫描和扔掉的数据就越多,所以查询就会越来越慢。
优化方案
方案1:子查询优化(延迟关联)
这是最常用的优化方案。思路就是:先用索引把需要的id查出来,然后再根据id去查完整的数据。
原来的慢查询:
SELECT * FROM orders ORDER BY id LIMIT 100000, 10;
优化后的写法:
SELECT o.* FROM orders o
INNER JOIN (
SELECT id FROM orders ORDER BY id LIMIT 100000, 10
) t ON o.id = t.id;
这样优化的好处是:子查询里的 SELECT id FROM orders ORDER BY id LIMIT 100000, 10 只需要扫描索引就能完成,不需要回表查完整数据,速度会快很多。然后再根据查出来的10个id,去关联完整的表数据,只需要10次回表操作就行。
这种优化方式也叫"延迟关联",是大表分页最常用的优化手段。
方案2:书签分页(记住上一页最后一条id)
如果你的业务场景允许(比如只允许"上一页"和"下一页",不允许直接跳页),那用书签分页是最快的。
思路就是:记住上一页最后一条数据的id,下一页直接从这个id后面开始查。
第一页:
SELECT * FROM orders ORDER BY id LIMIT 10;
假设第一页最后一条数据的id是100。
第二页:
SELECT * FROM orders WHERE id > 100 ORDER BY id LIMIT 10;
这样不管你翻到第几页,查询速度都是一样快的,因为每次都是从某个id开始往后查10条,MySQL不需要扫描前面的几十万条数据。
这种分页方式的缺点是不支持直接跳页,只能一页一页往后翻。很多信息流产品(比如朋友圈、抖音)都是用这种分页方式。
方案3:覆盖索引
如果你查询的字段刚好都在索引里,那MySQL就不需要回表查完整数据了,速度会快很多。
比如你只需要查 id, title, create_time 这几个字段,那你可以建一个联合索引:
CREATE INDEX idx_title_time ON orders(title, create_time);
然后查询的时候只查这几个字段:
SELECT id, title, create_time FROM orders ORDER BY create_time LIMIT 100000, 10;
这样MySQL直接从索引里就能拿到所有需要的数据,不需要回表,查询速度会快很多。
方案4:禁止跳页查询
如果你的业务场景允许,可以考虑禁止用户直接跳页。比如只允许用户看前100页,或者只允许"上一页"和"下一页"。
因为翻到第1000页、第10000页的用户其实很少,大部分用户都是看前几页。禁止跳页可以避免大量深分页查询拖慢数据库。
常见坑
坑1:用LIMIT offset的时候,offset越大越慢
很多同学不知道LIMIT offset的原理,以为MySQL直接就跳过了前面的offset条数据。其实不是的,MySQL是先把前面的offset条数据都查出来,然后再扔掉。所以offset越大,查询越慢。
坑2:优化了子查询,但是子查询里没有用到索引
如果你的ORDER BY字段没有建索引,那就算用了子查询优化,还是会很慢。一定要确保ORDER BY的字段有索引。
坑3:书签分页的时候,id不是连续的
书签分页的前提是你的id是连续的、递增的。如果你的业务场景里id可能会被删除,导致id不连续,那书签分页可能会漏数据。这时候需要根据实际情况调整分页策略。
坑4:用count(*)查总页数很慢
大表查总页数也很慢,比如 SELECT COUNT(*) FROM orders,几百万行的表可能要查好几秒。
这个优化方案是:
- 如果业务不需要精确的总页数,可以用"大约XX万条"来代替
- 或者单独建一张表存总条数,定期更新
- 或者用explain估算一下大概的行数
总结
MySQL大表分页越查越慢的问题,核心原因就是LIMIT offset会先扫描offset条数据然后扔掉,offset越大越慢。
常用的优化方案:
- 子查询优化(延迟关联):先查id,再关联完整数据
- 书签分页:记住上一页最后一条id,下一页从这个id开始查
- 覆盖索引:只查索引里有的字段,避免回表
- 禁止跳页:不允许直接翻到第1000页
根据你的业务场景选择合适的优化方案,大表分页查询慢的问题就能很好地解决了。
遇到问题加QQ23979811 协助处理