mysql执行in子查询易变为dependent subquery,因其默认对外层每行重复执行子查询,导致高延迟;即使5.6+支持semi-join优化,也受限于子查询结构、结果集大小及索引缺失等因素。

为什么MySQL执行IN子查询容易变成DEPENDENT SUBQUERY
当写 WHERE id IN (SELECT user_id FROM orders WHERE status = 'paid') 时,MySQL 5.7 及更早版本默认按“对外层每行都重跑一次子查询”的方式执行——也就是 DEPENDENT SUBQUERY。50 万用户 × 平均 8ms = 4 秒以上纯等待,不是计算慢,是重复调度慢。
即使 MySQL 5.6+ 引入了 semi-join 优化,也只在子查询结构简单、结果集小、且优化器估算代价低时才自动触发。一旦子查询含 GROUP BY、LIMIT、或关联字段没索引,优化器大概率退回到老式嵌套循环。
- 查执行计划:看到
type=DEPENDENT SUBQUERY或Extra列含Using temporary; Using filesort就是典型信号 - 别依赖“自动优化”:EXPLAIN 后发现
key为空、rows过大,说明没走索引,得手动改写 - MySQL 8.0 的 CTE 默认不物化,
WITH t AS (SELECT ...)在大结果集下可能比子查询还慢
INNER JOIN如何避免重复执行和临时表膨胀
INNER JOIN 让优化器能一次性规划整个执行路径:选驱动表、下推过滤条件、复用索引。比如把 orders.status = 'paid' 放进 ON 子句:JOIN orders o ON u.id = o.user_id AND o.status = 'paid',MySQL 就可能用到 (user_id, status) 联合索引,而不是先扫全表再过滤。
- 子查询物化会生成临时表,若没显式建索引,后续 JOIN 就是全表扫描
- JOIN 的哈希连接(Hash Join)或排序合并(Sort-Merge Join)在内存充足时比嵌套循环快一个数量级
- 临时表生命周期短,但若没主键或索引,
JOIN temp_ids仍可能走 ALL 类型
改写时最容易踩的三个语义坑
很多人一换完 JOIN 就上线,结果数据对不上、字段报错、NULL 被意外丢弃——不是语法错,是语义变了。
-
IN天然去重,INNER JOIN遇到一对多(如一个用户多笔订单)会放大结果行数;需加DISTINCT或确认业务允许重复 - 原逻辑要查“没订单的用户”,却写了
INNER JOIN,直接漏掉全部数据;该用LEFT JOIN ... WHERE o.user_id IS NULL - 两表都有
id字段,SELECT * FROM users u JOIN orders o ON u.id = o.user_id会报Column 'id' in field list is ambiguous;必须显式写u.id或o.id
UPDATE语句里用JOIN替代子查询的写法差异
MySQL 和 PostgreSQL 对 UPDATE + JOIN 的支持完全不同,写错直接报错或更新错行。
- MySQL:必须给被更新表起别名,
UPDATE orders t1 JOIN customers t2 ON t1.customer_id = t2.id SET t1.status = 'done';JOIN后不能跟AS - PostgreSQL:用
UPDATE ... FROM,被更新表不能出现在FROM列表里,否则报table name "orders" specified more than once - 两者都要求
ON字段有索引,否则仍是全表嵌套循环;若子查询含MAX(created_at)这类聚合,JOIN 无法直接替代,得先物化为临时表
真正卡住性能的,往往不是语法本身,而是改写后没验证执行计划是否真走了索引、没检查字段歧义、也没确认 NULL 行是否被意外过滤。上线前跑一遍 EXPLAIN,看 type 是不是 ref 或 range,key 列有没有值,比背一百条优化口诀都管用。











