优先用JOIN替代IN子查询,避免物化临时表和重复执行;相关子查询需转为LEFT/INNER JOIN;子查询只取必要列,确保关联字段有索引。

IN 子查询慢?优先改写成 JOIN,别硬扛。
用 JOIN 替代 IN 子查询最稳妥
IN 子查询在数据量稍大时极易触发物化(Using temporary),尤其是子查询结果集超过几千行。MySQL 会把子查询结果写入临时表,再逐行比对,IO 和 CPU 开销都高。
- 外层表和子查询表有明确关联字段(如 user_id)时,直接改 JOIN 是最快路径
- 确保关联字段上有索引,否则 JOIN 也会退化为嵌套循环
- 如果子查询带聚合(如 GROUP BY),记得把聚合逻辑移到 JOIN 的派生表里,例如:
SELECT o.id, o.total FROM orders o JOIN (SELECT user_id, SUM(amount) AS total FROM payments GROUP BY user_id) p ON o.user_id = p.user_id;- 不要盲目加
DISTINCT 去重——先确认语义是否真需要;多余去重会强制排序或临时表
相关子查询必须警惕执行次数
EXISTS 或 IN 引用外层列时,就是相关子查询。它不是执行一次,而是外层每行都执行一遍。
- 比如 SELECT * FROM users u WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id),users 表有 10 万行,子查询就执行 10 万次
- 即使 orders.user_id 有索引,单次快不代表总量快;10 万次索引查找 + 回表,远不如一次 JOIN 扫描
- 若语义允许,一律转成 LEFT JOIN ... WHERE ... IS NOT NULL 或直接 INNER JOIN
- NOT EXISTS 更危险:无法用索引高效跳过,常导致全表扫描,优先考虑反向 LEFT JOIN ... IS NULL
SELECT * + 子查询 = 双重回表风险
子查询本身若返回多列,又在外层用 SELECT *,很容易触发两次回表。
- 例如 SELECT * FROM products WHERE category_id IN (SELECT id, name FROM categories WHERE level = 3),子查询的 name 字段完全无用,却迫使 MySQL 物化整行
- 正确写法只取必要列:SELECT * FROM products WHERE category_id IN (SELECT id FROM categories WHERE level = 3)
- 更进一步,如果外层也要查 categories.name,那就直接 JOIN,避免两次索引查找+回表
真正卡住性能的,往往不是子查询语法本身,而是没意识到它背后那张被反复创建又丢弃的临时表,以及每次执行都重新走一遍的索引查找路径。


















