在mysql日常开发中,子查询写起来直观,但执行效率差异很大。理解其子查询的执行机制并做针对性改写,是提升查询性能的重要手段。

子查询在mysql中的常见形式
mysql中的子查询通常出现在where、from以及select子句中。不同位置对优化器的影响不同。
- where中的标量子查询:返回单行单列,容易引发逐行执行。
- from中的派生表:子查询生成临时表,可能丢失索引。
- exists子查询:常用于判断存在性,可被优化为半连接。
容易引起性能问题的写法
下面这段查询在where中使用了in加子查询,当子查询返回数据量较大时,外层查询可能对每一行都执行一次子查询。
select user_id, user_name from t_user where user_id in ( select user_id from t_order where amount > 1000 );
如果t_order上没有合适索引,子查询会全表扫描,再与外层做嵌套循环,响应时间随数据量线性增长。
使用exists改写提升效率
对于存在性判断,exists通常比in更高效,因为mysql在找到第一条匹配记录后即可停止扫描。
select u.user_id, u.user_name from t_user u where exists ( select 1 from t_order o where o.user_id = u.user_id and o.amount > 1000 );
在o.user_id上建立索引后,exists子查询能快速定位,避免生成大中间集。
派生表使用索引的技巧
from中的子查询会生成派生表,mysql旧版本常无法利用内部索引。可以通过先聚合再关联的方式减少派生表行数。
select u.user_id, s.total_amount from t_user u join ( select user_id, sum(amount) as total_amount from t_order group by user_id ) s on u.user_id = s.user_id where s.total_amount > 5000;
确保t_order的user_id有索引,且group by字段有序,能显著降低派生表体积。
用关联查询替代子查询
某些标量子查询可以改写为left join,让优化器选择更优的执行计划。
select u.user_id,
(select count(*) from t_order o where o.user_id = u.user_id) as cnt
from t_user u;
改写为:
select u.user_id, count(o.order_id) as cnt from t_user u left join t_order o on o.user_id = u.user_id group by u.user_id;
执行计划验证
任何改写都应使用explain查看type、key、rows字段,确认是否使用了索引以及扫描行数是否下降。只有基于执行计划的对比,才能判断子查询优化是否真正生效。
| 检查项 | 优化前 | 优化后 |
|---|---|---|
| type | ALL | ref |
| key | NULL | idx_user_id |
| rows | 100000 | 120 |
子查询不是性能杀手,错误的使用方式才是。结合索引与执行计划做针对性改写,才能发挥mysql的处理能力。