网站开发实训基本要求,网站不换域名换空间,北京环球影城必须存包的项目,青岛栈桥导游词写 SQL 的时候#xff0c;大家都有个肌肉记忆#xff1a;如果只需要一条数据#xff0c;一定要加上 LIMIT 1。这听起来非常合理#xff0c;毕竟数据库只要找到一条满足条件的记录#xff0c;就可以收工回家#xff0c;不用再把剩下的几百万行数据扫一遍#xff0c;既省 …写 SQL 的时候大家都有个肌肉记忆如果只需要一条数据一定要加上LIMIT 1。这听起来非常合理毕竟数据库只要找到一条满足条件的记录就可以收工回家不用再把剩下的几百万行数据扫一遍既省 IO 又省 CPU。但前两天排查线上问题时我遇到了一个非常有意思的案例加了 LIMIT 1查询反而慢了 50 倍。这就好比你为了抄近道走了一条小路结果发现这条路堵得水泄不通比走大路还慢。排查一圈发现是 MySQL 优化器的问题1. 还原一下现场业务场景很简单我们要查某个用户最近的一笔“处理中”的订单。订单表orders大概有 500 万数据表里有两个关键索引idx_user_status(user_id, status)用来根据用户和状态过滤数据。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 看看因为优化器有时候也是会做出抽风的事