在日常开发中,写 SQL 查询时用子查询很常见。比如查某个部门薪资最高的员工,可能会先查出部门最高工资,再找对应的人。这种嵌套写法看起来逻辑清晰,但性能上真没问题吗?特别是,子查询会不会让数据库“绕过”原本建好的索引?
子查询不是原罪,关键看怎么写
子查询本身不会直接导致索引失效。真正影响索引使用的,是子查询的结构和执行方式。MySQL 或其他数据库在处理子查询时,会尝试优化执行计划,但有些写法会让优化器“无从下手”。
举个例子,有张员工表 employees,有个索引在 salary 字段上:
SELECT name, salary
FROM employees
WHERE salary = (
SELECT MAX(salary)
FROM employees
WHERE dept_id = 10
);
这个查询中,子查询单独执行,结果是一个值。外部查询依然能使用 salary 上的索引,因为条件是等值匹配。这种情况下,索引照常生效。
什么时候索引会被“绕开”?
问题通常出现在关联子查询里。比如下面这种写法:
SELECT e1.name, e1.salary
FROM employees e1
WHERE e1.salary > (
SELECT AVG(e2.salary)
FROM employees e2
WHERE e2.dept_id = e1.dept_id
);
这里子查询依赖外部查询的 e1.dept_id,每扫描一行都要重新执行一次子查询。这种“相关子查询”可能导致数据库无法有效利用索引,尤其是当 dept_id 没有索引,或者统计函数让优化器难以估算行数时。
换个写法,效果可能大不同
上面的例子如果改成 JOIN 形式,往往更高效:
SELECT e1.name, e1.salary
FROM employees e1
JOIN (
SELECT dept_id, AVG(salary) as avg_sal
FROM employees
GROUP BY dept_id
) e2 ON e1.dept_id = e2.dept_id
WHERE e1.salary > e2.avg_sal;
子查询变成了派生表,先计算出每个部门的平均工资,再和主表关联。这时候,如果 dept_id 有索引,JOIN 过程可以充分利用它,整体性能通常比嵌套循环好得多。
实际排查建议
别光靠猜,用 EXPLAIN 看执行计划最实在。关注 type 是不是 index 或 ref,有没有出现 Using temporary 或 Using filesort。如果子查询那一行显示 DEPENDENT SUBQUERY,就得小心了,说明它在反复执行。
另外,索引能不能用,也跟数据分布有关。比如子查询返回上千条数据,外部查询用 IN 去匹配,即使字段有索引,数据库也可能觉得全表扫描更快。
归根结底,子查询不是不能用,但得清楚它的执行逻辑。写的时候多想想:这句 SQL 是不是让数据库“重复劳动”了?有没有更平铺直叙的方式把事情说清楚?很多时候,换个思路,性能立马不一样。