当前位置: 代码网 > it编程>数据库>Mysql > 加了 limit 1 查询竟然变慢了的原因分析及解决办法

加了 limit 1 查询竟然变慢了的原因分析及解决办法

2026年04月09日 Mysql 我要评论
写 sql 的时候,大家都有个肌肉记忆:如果只需要一条数据,一定要加上limit 1。这听起来非常合理,毕竟数据库只要找到一条满足条件的记录,就可以收工回家,不用再把剩下的几百万行数据扫一遍,既省 i

写 sql 的时候,大家都有个肌肉记忆:如果只需要一条数据,一定要加上 limit 1

这听起来非常合理,毕竟数据库只要找到一条满足条件的记录,就可以收工回家,不用再把剩下的几百万行数据扫一遍,既省 io 又省 cpu。

但前两天排查线上问题时,我遇到了一个非常有意思的案例:加了 limit 1,查询反而慢了 50 倍。

这就好比你为了抄近道走了一条小路,结果发现这条路堵得水泄不通,比走大路还慢。

1. 还原一下现场

业务场景很简单:我们要查某个用户最近的一笔“处理中”的订单。

订单表 orders 大概有 500 万数据,表里有两个关键索引:

  1. idx_user_status(user_id, status):用来根据用户和状态过滤数据。
  2. idx_create_time(create_time):用来按时间排序。

代码里的 sql 是这么写的:

select id, order_no, amount 
from orders 
where user_id = 10086 and status = 1 
order by create_time desc 
limit 1;

这条 sql 上线后,直接触发了慢查询报警,耗时飙到了 2.5 秒

为了搞清楚原因,我试着把 limit 1 去掉,裸跑了一次:

select id, order_no, amount 
from orders 
where user_id = 10086 and status = 1 
order by create_time desc;

结果只要 50 毫秒。

加了 limit 也就是想省点事,反而变慢了呢?

2. explain 分析

遇到这种诡异的事,第一反应肯定是看执行计划。对比了一下两条 sql 的 explain 结果,真相立刻浮出水面:

  • 没加 limit时:   mysql 选择了 idx_user_status 索引。它先精准地把这个用户状态为 1 的订单找出来(只有几十条),然后在内存里排个序。因为数据量少,这个排序几乎瞬间完成。
  • 加了 limit 后:  mysql 居然放弃了精准过滤,改用了 idx_create_time 索引。 它的思路变成了,按时间倒序扫描全表,一边扫一边检查这是不是该用户的订单。

为什么优化器会觉得第二种方案更好?

这里我们得站在 mysql 优化器的角度想一想。它在做决策时,其实是在做一道算术题:

  • 方案 a 走过滤索引:先把符合条件的数据全找出来,再排序。 缺点:如果符合条件的数据很多,排序成本会很高。
  • 方案 b 走时间索引 + limit:既然你只要 1 条数据,而且要求按时间倒序,那我就顺着时间索引往回找。优点:天然有序,不用再排序了。

优化器其实在赌,优化器觉得,运气只要不是太差,应该很快就能碰到一条满足 user_id 和 status 的记录。

问题就出在这个赌注上。

在这个案例里,用户 10086 是个老用户,他最近的一笔“处理中”的订单,其实是一年前下的。

于是,mysql 顺着时间索引,从今天的数据开始往回扫,扫了昨天、上周、上个月……一直扫了 200 多万行数据,才终于在去年的数据里找到了那条记录。

这就是为什么加了 limit 1 反而变成了全表扫描级别的慢查询。

3. 怎么解决?

既然知道了是优化器选错路了,那我们的思路就是帮它纠正过来。

方法一:简单粗暴 force index

既然优化器甚至不清楚,那我们就直接教它做事。

select ... from orders force index (idx_user_status) ...

这就相当于在导航里强制选定路线。优点是立竿见影,缺点是代码不够优雅,如果以后索引名改了,这行代码会报错。

方法二:最稳妥的联合索引**

优化器之所以纠结,是因为现有的索引没法同时满足“过滤”和“排序”。

我们可以建一个联合索引:(user_id, status, create_time)

在这个索引里,数据先按用户和状态聚在一起,内部再按时间排序。mysql 只要用这个索引,既能精准定位,又不用额外排序,这才是最完美的解法。

方法三:子查询的小技巧

如果你不想改表结构,还有一个巧妙的写法:

select * from (
    select ... from orders 
    where user_id = 10086 and status = 1 
    order by create_time desc
) as tmp 
limit 1;

我们用一个子查询先把数据找出来,这时候 mysql 会乖乖走过滤索引,然后再在外层取 limit 1。这就相当于人为地切断了 limit 对内层索引选择的干扰。

写在最后

limit 1 确实是个好习惯,但也要看场景。

在这个案例里,mysql 的优化器因为过度自信,觉得“很快就能找到这一条”,结果在数据分布不均匀的情况下翻了车。

下次如果再遇到加了限制反而变慢的问题,直接用 explain 看看,因为优化器有时候也是会做出抽风的事!

到此这篇关于加了limit 1查询竟然变慢了的原因分析及解决办法的文章就介绍到这了,更多相关limit1查询变慢解决内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com