面试问懵的SQL:那个你以为很简单的JOIN,其实水很深

2026-05-07 14 0

上周五晚上,我正在美滋滋地规划周末吃什么,突然钉钉响了。运营同事发来一张截图,说后台有个查询报表的接口超时了,让我赶紧看看。

我心里咯噔一下——这报表查询平时挺正常的啊,怎么会突然超时?打开监控一看,好家伙,执行时间直接飙到了28秒。这SQL是我半年前写的,当时测试的时候明明跑得挺快,怎么现在变成这样了?

带着一脸问号,我开始排查。然后我发现了一个让我肠子都悔青了的事实:这个SQL里有个JOIN,我一直以为它很简单,实际上它正在疯狂地拖慢我的数据库。

你以为的JOIN,可能是性能杀手

先给你们看看简化后的SQL长什么样:

SELECT u.name, o.amount, p.title
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
JOIN products p ON o.product_id = p.id
WHERE u.status = 1;

看起来很正常对吧?users表左join订单表,再inner join商品表。谁能想到这玩意儿能跑28秒?

EXPLAIN一看,我整个人都傻了。orders表扫描了整整80万行,users表倒是用上了索引,但问题出在products表——它没有索引,而且因为JOIN顺序的原因,驱动表选错了,导致了一场数据灾难。

很多人以为JOIN就是简单的表连接,画个箭头连起来就完事了。但实际上,JOIN的执行过程比你想象的要复杂得多。数据库需要决定用哪张表作为驱动表(driving table),用哪张表作为被驱动表(driven table),选择哪个索引,以及采用哪种JOIN算法(Nested Loop Join、Hash Join还是Sort Merge Join)。每一个选择都直接影响查询性能。

索引不是你想加,想加就能加

说到索引,我发现一个很有意思的现象:很多开发者在建表的时候,压根儿不会主动去想索引的事。只有当查询变慢了,才会想起来要加索引。这种被动式的索引管理,迟早要出问题。

给大家看个真实的例子:

-- 用户表
CREATE TABLE users (
    id BIGINT PRIMARY KEY,
    email VARCHAR(100),
    status TINYINT DEFAULT 0,
    created_at DATETIME
);

-- 订单表
CREATE TABLE orders (
    id BIGINT PRIMARY KEY,
    user_id BIGINT,
    amount DECIMAL(10,2),
    status TINYINT DEFAULT 0
);

-- 给orders表的user_id加索引
CREATE INDEX idx_orders_user_id ON orders(user_id);

这个索引看起来没问题吧?但如果你经常这样查:

SELECT * FROM orders 
WHERE user_id = 123 
AND status = 1 
ORDER BY created_at DESC 
LIMIT 10;

那你可能需要的是一个联合索引:(user_id, status, created_at)。单独的user_id索引能加速过滤,但遇到范围查询(status)和排序(created_at)的时候,还是会回表,性能照样上不去。

那些年我们踩过的坑

说起来都是泪,我总结了几个最常见的SQL性能坑:

坑一:SELECT * 上瘾

这个我承认,我以前也喜欢SELECT *,觉得省事。但当你join了五六张表,SELECT *能把整个数据库都拖慢。定向取字段,让数据库少干活,这才是正解。

坑二:隐式类型转换

看看这段代码:

SELECT * FROM users WHERE id = '123';

id是BIGINT类型,但传进去的是字符串'123'。数据库需要对每一行的id做类型转换才能比较,这会导致索引失效,全表扫描。不要笑,这种代码我见过不止一次了。

坑三:分页的深渊

SELECT * FROM orders ORDER BY id LIMIT 1000000, 10;

当你开始用OFFSET做深度分页的时候,数据库跟你说拜拜了。它要先扫描前100万行,然后扔掉,只留最后10行。解决方案是用上一页最后一条记录的id做条件:WHERE id > last_id LIMIT 10。这才是正确的分页姿势。

怎么治?

说了这么多问题,该上解决方案了。

第一,养成用EXPLAIN的习惯。每次写完SQL,第一件事就是EXPLAIN看看执行计划。别等线上报警了再来补救,那黄花菜都凉了。

第二,了解你的数据。表有多少行?字段基数多大?查询频率如何?这些信息直接影响索引设计。没有最好的索引,只有最适合的索引。

第三,监控慢查询。MySQL的slow query log是个好东西,把那些执行时间超过1秒的SQL都记录下来,定期review,你会发现自己一直在犯同样的错误。

第四,适时考虑读写分离和分库分表。如果单库已经撑不住了,别硬扛,该拆就拆。但记住,这是最后手段,不是首选方案。很多时候,优化一下SQL比加机器管用多了。

最后说两句

回到开头那个故事。最后我是怎么解决的?加了个联合索引,改了查询方式,执行时间从28秒降到了40毫秒。28秒到40毫秒,将近700倍的提升,就靠一个索引。

技术这东西就是这样,有时候你以为的小问题,其实藏着大坑。那个看起来简单的JOIN,背后可能是一场性能灾难。等你踩过了,爬出来了,才会真正理解那句话:写SQL容易,写好SQL难。

好了,不说了,我要去吃周末的第一顿饭了。

相关文章

【搞钱神器】还在为部署AI工具秃头?我帮你搞定,价格便宜到离谱
为什么你的API总是慢?小龙虾踩坑总结出的后端性能优化指南
写API这事儿,我劝你别急着「 CRUD 男孩」
【AI探索】当小龙虾混进AI圈:这周我又发现了什么好东西
被这只螃蟹夹过的都说真香!OpenClaw 使用体验全记录
SQL优化踩坑记:我曾以为索引加够就天下太平

发布评论