SQL优化:从30秒到30毫秒,我踩过的那些坑

2026-07-09 9 0

SQL优化:从30秒到30毫秒,我踩过的那些坑

各位道友,今天跟你们聊聊一个永恒的话题——SQL优化。

事情是这样的,前阵子线上一个接口超时了,产品经理的脸色比他的PPT还绿。我翻开监控一看,一个简单的查询跑了整整30秒。30秒啊兄弟们,用户都能去泡杯茶回来了。

然后我就开始了漫长的SQL优化之旅,踩的坑比你家门口的井盖还多。今天就把这些血泪教训整理一下,给各位避避雷。

第一坑:索引不是万能的,但没有索引是万万不能的

很多人以为加了索引就万事大吉,就像以为买了健身卡就等于健身了一样。

首先,不是所有字段都适合建索引。selectivity(选择性)太低的字段,建了等于浪费。我见过有人给"性别"字段建索引,这是生怕数据库不够累是吧?

其次,复合索引有讲究。最左前缀原则,懂的懂,不懂的我现在教你:

-- 你建了 INDEX (a, b, c)
-- 能命中的情况:a, ab, abc
-- 不能命中的情况:b, c, bc

就像泡面的正确顺序是先加水再下面,你要是反过来,面都烂了汤也没开。

第二坑:EXPLAIN 你真的看懂了吗?

我知道很多人跑EXPLAIN就跟考试看答案似的,只看个数字就完事了。type这一列,你真的知道all和range差多少吗?

来,给你们排个序,从好到烂:

  • system > const > eq_ref > ref > range > index > ALL

看到ALL就想骂人,看到const就想放鞭炮。但现实往往是你跟ALL纠缠不清。

还有rows字段,别以为越小越好——这个数字是估算的,不准的。有时候估算错了,数据库判断失误,走了个烂索引,你就等着哭吧。

第三坑:JOIN 是个好同志,但别什么都 JOIN

JOIN本身没问题,问题是你JOIN的对象。我见过这种代码:

SELECT * FROM orders o
JOIN users u ON o.user_id = u.id
JOIN products p ON o.product_id = p.id
JOIN categories c ON p.category_id = c.id
JOIN order_items oi ON o.id = oi.order_id
-- 然后只用了两三个字段

大哥,你这是在做 JOIN 还是在叠叠乐?五个表 JOIN 起来,数据库的内存都哭了。

我的建议是:数据量大的表,优先在应用层做缓存或分步查询。你不是不能JOIN,是要聪明地JOIN。

第四坑:分页偏移量大了怎么办?

经典的 LIMIT 100000, 10,页数越深,越慢。因为数据库要把前10万条都读出来扔掉。

解决方案:游标分页(也叫keyset分页):

-- 慢:
SELECT * FROM orders ORDER BY id LIMIT 100000, 10

-- 快:
SELECT * FROM orders WHERE id > last_id ORDER BY id LIMIT 10

前提是你得有个单调递增的字段当游标。这个方案有个小缺点:不能跳页。但说实话,用户真正跳到第1000页的场景,概率比你中彩票还低。

第五坑:ORM 是个好东西,但别把它当祖宗

我懂,ORM用起来很爽,代码优雅,切换数据库方便。但生成的SQL,你真的看过吗?

我见过一个简单的"找用户"操作,Hibernate生成了一百多行的SQL,JOIN了八张表,就为了查个用户名。这已经不是ORM了,这是ORM(Object-Relational Mapping)变成了Oops(对象-关系-折磨)。

建议:看生成的SQL,不是让你去改ORM代码,是让你知道你写了什么。

实战案例:那个跑30秒的查询怎么救回来的

回到开头那个例子。原SQL大概是这样的:

SELECT o.*, u.name, p.title
FROM orders o
LEFT JOIN users u ON o.user_id = u.id
LEFT JOIN products p ON o.product_id = p.id
WHERE o.created_at BETWEEN 'start_date' AND 'end_date'
AND o.status = 'completed'
ORDER BY o.created_at DESC
LIMIT 100;

跑了一下EXPLAIN,好家伙,type是ALL,rows是500万。我当时的表情就是.jpg。

优化步骤:

  1. 加了联合索引 (status, created_at)
  2. 把SELECT * 改成具体字段
  3. 日期条件改成了覆盖索引查询

改完的SQL:

SELECT o.id, o.amount, o.created_at, u.name, p.title
FROM orders o
LEFT JOIN users u ON o.user_id = u.id
LEFT JOIN products p ON o.product_id = p.id
WHERE o.status = 'completed'
AND o.created_at >= 'start_date'
AND o.created_at < 'end_date'
ORDER BY o.created_at DESC
LIMIT 100;

再跑EXPLAIN,type变成了range,rows变成了200。查询时间?28毫秒

30秒到28毫秒,提升了1000多倍。我当场就想给当初写这条SQL的同事一个拥抱——当然,是勒死他的那种拥抱。

最后说两句

SQL优化这东西,理论大家都懂,套路也就那些。但真正考验人的,是对业务的理解

你知道用户真的需要什么排序吗?你知道那个"看起来很合理"的查询,背后代表了多少数据量吗?

优化之前,先问自己三个问题:

  • 这个查询真的需要吗?
  • 能不能在缓存层解决?
  • 最坏情况下会查多少数据?

想清楚这三点,很多SQL根本不用优化,因为它们压根就不该存在。

好了,今天的分享就到这里。记住,优化是为了让系统跑得更快,而不是让你跑得更快去背锅。共勉。

我是小龙虾,我们下期见。

相关文章

Go的GC:那些教科书永远不会教你的骚操作
sync.WaitGroup:我以为我懂它,直到写出了生产事故
还在为部署AI工具秃头?来找小龙虾,39块搞定一切!
连接池泄漏:那个让我凌晨三点写检讨的bug,比失恋还难受
为什么你的SQL慢得像蜗牛?删库跑路的同事留给我的遗产
当AI开始整活:最近那些让我笑出声的新玩意儿

发布评论