减少子查询的关键是把嵌套逻辑“拉平”,用连接(JOIN)或临时表替代,让MySQL执行计划更高效、更可控。
用JOIN代替相关子查询
相关子查询(即子查询依赖外层表字段)每行执行一次,性能极差。多数场景可改写为LEFT JOIN或INNER JOIN。
原写法:SELECT id, name FROM users WHERE id IN (SELECT user_id FROM orders WHERE status = 'paid') 优化后:SELECT DISTINCT u.id, u.name FROM users u INNER JOIN orders o ON u.id = o.user_id WHERE o.status = 'paid' 注意:用DISTINCT防重复;若需保留无订单用户,改用LEFT JOIN + WHERE o.user_id IS NOT NULL用派生表(Derived Table)替代非相关子查询
不依赖外层的子查询(如聚合结果),可提前算好作为临时结果集,避免多次计算。
原写法:SELECT name, (SELECT AVG(score) FROM exams) AS avg_score FROM students 优化后:SELECT s.name, dt.avg_score FROM students s CROSS JOIN (SELECT AVG(score) AS avg_score FROM exams) dt CROSS JOIN更语义清晰;也可用JOIN(无ON条件)或直接在SELECT中用变量(需确保单行结果)用EXISTS替代IN(尤其大表子查询)
当子查询返回大量ID,且只需判断存在性时,EXISTS通常比IN更快,因为它能短路退出。
原写法:SELECT * FROM products WHERE category_id IN (SELECT id FROM categories WHERE active = 1) 优化后:SELECT * FROM products p WHERE EXISTS (SELECT 1 FROM categories c WHERE c.id = p.category_id AND c.active = 1) 确保子查询中关联字段有索引(如categories.id、products.category_id)必要时拆分+临时表缓存中间结果
复杂多层嵌套子查询,尤其是含GROUP BY、窗口函数或重复计算的,可先存入临时表,再JOIN使用。
例如统计每个用户最近3笔订单总金额,可先建临时表:CREATE TEMPORARY TABLE tmp_last3 AS SELECT user_id, SUM(amount) sum_amt FROM (SELECT user_id, amount, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) rn FROM orders) t WHERE rn 再与users表JOIN:SELECT u.name, t.sum_amt FROM users u LEFT JOIN tmp_last3 t ON u.id = t.user_id 临时表自动索引主键,支持WHERE/JOIN加速,也便于EXPLAIN分析