MySQL查询里用Limit参数那些事儿,怎么能让速度快点更省资源
- 问答
- 2026-01-05 15:17:52
- 20
说到MySQL里的LIMIT,大家肯定不陌生,就是用来限制查询结果返回多少条数据的,比如LIMIT 10就是只拿前10条,LIMIT 20, 10就是从第21条开始拿10条,这功能看起来简单,但用不好,尤其是在数据量大的表里,分页一深,速度就会慢得让你怀疑人生,今天我们就专门聊聊,怎么让它快起来,省点服务器资源。
LIMIT为什么会慢?问题的根子在哪里?
很多人有个误解,以为LIMIT 10000, 10就是只找10条数据,很快,其实不然,根据MySQL官方文档和大量实践案例(高性能MySQL》这本书里就详细讨论过),MySQL在处理LIMIT offset, size的时候,它实际的工作流程是这样的:它需要先扫描到offset + size条数据,然后扔掉前面的offset条,只返回最后的size条。
举个例子,你执行SELECT * FROM articles ORDER BY id DESC LIMIT 100000, 20;,你以为数据库只处理20条数据,但实际上,它需要先扫描100000+20=100020条符合条件的数据记录(即使你只选了,排序过程也需要处理这么多),然后丢弃前10万条,把最后20条给你,这个“扫描并丢弃”前10万条的过程,就是性能瓶颈所在,如果offset值非常大,比如在做深度分页时,这个开销是极其恐怖的,CPU、内存、磁盘IO压力都会很大。
让LIMIT快起来的几个实用招数
知道了病根,就能对症下药了,核心思路就一个:想尽办法避免扫描和丢弃大量的数据。
第一招,也是最有效的一招:用“游标”分页,或者叫“seek method”(寻找法)
这个方法在Percona的博客和很多技术分享里被反复推荐,它完全摒弃了传统的LIMIT offset, size做法,不依赖偏移量,而是利用索引和上一页最后一条记录的位置来“锚定”下一页的起点。
具体怎么做呢?假设我们有一个文章表,用自增主键id排序做分页。
- 传统慢方法:
SELECT * FROM articles ORDER BY id DESC LIMIT 100000, 20; - 游标快方法: 当你翻到第N页时,你知道当前页最后一条记录的
id是last_id(比如是5000),那么下一页的查询就不是计算偏移量,而是:SELECT * FROM articles WHERE id < 5000 ORDER BY id DESC LIMIT 20;
你看,这个查询直接通过WHERE id < 5000利用主键索引快速定位到了起始位置,然后往后扫描20条就行了,它根本不需要知道前面有多少条数据,也完全跳过了“丢弃”大量数据的过程,无论你翻到多深的页码,速度都几乎一样快,因为每次都是通过索引直接“寻找”起点。
这种方法要求你的排序字段(通常是主键或时间戳)上有索引,并且分页逻辑是连续的(不能跳页),在Web应用中,这通常是可行的,因为用户都是一页一页往下翻。

第二招:和ORDER BY配合时,确保排序能用上索引
LIMIT的慢,很多时候是慢在排序上,如果ORDER BY的字段没有索引,MySQL就必须进行“文件排序”(filesort),这通常意味着要在内存或磁盘上创建一个临时表来排序所有数据,然后再应用LIMIT,当数据量大时,这个排序过程本身就非常耗时耗资源。
一个基本原则是:你的ORDER BY子句后面的字段,应该建立合适的索引。 让MySQL直接按索引顺序读取数据,这样在应用LIMIT时,它只需要按顺序扫描索引,找到需要的几条数据后就可以立刻停止,效率极高,比如ORDER BY create_time DESC,就给create_time字段加个索引。
第三招:使用覆盖索引,减少回表
这是一个进阶优化,即使ORDER BY用上了索引,查询SELECT *依然会慢,因为普通索引(非聚簇索引)只存储了索引字段和主键值,MySQL先通过索引找到了需要的主键ID,然后再根据这些ID回到主键索引(聚簇索引)上去取回所有列的数据,这个过程叫“回表”。
如果我们要查的字段很多,回表次数多了,开销也不小,这时候可以考虑使用“覆盖索引”,所谓覆盖索引,就是指要查询的字段,全部包含在了一个索引里面,这样MySQL只需要扫描这个索引就能拿到所有数据,根本不需要回表。

还拿文章表举例,如果你的分页列表只需要显示id, title, create_time这三个字段,那么可以创建一个联合索引(create_time, id, title),然后查询写成:
SELECT id, title, create_time FROM articles ORDER BY create_time DESC LIMIT 100000, 20;
这时,MySQL可以完全在(create_time, id, title)这个索引上完成排序和数据的获取,速度会比SELECT *快上一个数量级。
第四招:对于实在无法避免的大Offset,考虑“延迟关联”
有时候我们确实需要SELECT *,又无法使用游标分页(比如需要支持跳页),offset值还特别大,这时可以试试“延迟关联”技巧,这个技巧在《高性能MySQL》中有描述。
思路是:先通过覆盖索引快速找出符合条件的主键ID,然后再通过主键ID去关联原表拿到所有数据。
查询改写为:
SELECT * FROM articles
INNER JOIN (
SELECT id FROM articles
ORDER BY create_time DESC
LIMIT 100000, 20
) AS tmp USING(id);
内层的子查询SELECT id只从索引中获取主键ID,因为数据量小(只有ID),即使offset很大,扫描索引的速度也相对较快,拿到这20个ID后,再通过INNER JOIN去原表精确匹配,这时候是20次主键查找,速度非常快,这种方法相当于把一个大开销的查询,拆成了两个相对较小的查询,总体耗时更短。
想让LIMIT快,关键就是减少不必要的扫描和计算。
- 首选“游标分页”,从根源上消灭大offset。
- 确保ORDER BY用上索引,避免昂贵的文件排序。
- 尝试使用覆盖索引,省去回表的开销。
- 不得已时,用延迟关联技巧来“曲线救国”。
这些方法都不是孤立的,在实际项目中可以根据具体情况组合使用,希望这些具体的思路能帮你解决实际问题。
本文由水靖荷于2026-01-05发表在笙亿网络策划,如有疑问,请联系我们。
本文链接:https://www.haoid.cn/wenda/75019.html
