子查询重写为JOIN需按语义选择连接类型、添加合适索引并处理重复与空值,否则可能性能更差或结果错误;WHERE IN子查询应改用INNER JOIN+DISTINCT,标量子查询需先聚合再LEFT JOIN,依赖子查询优先用EXISTS或CTE,改写后必须用EXPLAIN验证执行计划。

子查询重写为 JOIN 不是机械替换,而是根据语义选对连接类型、加对索引、处理好重复和空值——否则性能可能更差,甚至结果出错。
WHERE IN (SELECT ...) 类型必须用 INNER JOIN + DISTINCT
这类子查询最常见,也最容易误改。MySQL 优化器对 WHERE id IN (SELECT user_id FROM order WHERE status = 'paid') 常生成全表扫描或重复执行,但直接改成 INNER JOIN 会放大主表行数(一个用户多笔订单 → 多行)。
- 必须加
DISTINCT或GROUP BY去重,否则结果集膨胀 -
order表需有联合索引(user_id, status),否则WHERE status = 'paid'无法走索引下推 - 若原查询允许
id为空或需保留无订单用户,就不能用INNER JOIN,得换LEFT JOIN+IS NOT NULL过滤
正确写法:
SELECT DISTINCT u.id, u.name FROM user u INNER JOIN `order` o ON u.id = o.user_id WHERE o.status = 'paid';
标量子查询(SELECT 子句中带 (SELECT ...))要用 LEFT JOIN + 聚合预处理
像 SELECT id, name, (SELECT MAX(create_time) FROM login_log WHERE user_id = user.id) AS last_login FROM user 这种,每行都触发一次子查询,N 行就是 N 次全表扫描(或索引扫描)。
- 不能直接
LEFT JOIN login_log,否则一个用户多条日志会产生多行,last_login字段会重复或被截断 - 必须先对
login_log按user_id分组聚合,再LEFT JOIN,确保一行一匹配 -
login_log(user_id, create_time)索引能加速MAX()计算,避免临时表
正确写法:
SELECT u.id, u.name, l.last_login FROM user u LEFT JOIN ( SELECT user_id, MAX(create_time) AS last_login FROM login_log GROUP BY user_id ) l ON u.id = l.user_id;
DEPENDENT SUBQUERY(依赖子查询)优先考虑 EXISTS 或 CTE,而非硬套 JOIN
当子查询里引用了外层字段(如 WHERE EXISTS (SELECT 1 FROM order o WHERE o.user_id = u.id AND o.amount > 100)),它本质是“是否存在”,不是“取哪些值”。强行 JOIN 容易引入歧义。
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
-
EXISTS语义清晰、通常能短路,且 MySQL 8.0+ 对其优化更好 - MySQL 8.0.1+ 可用 CTE 预计算子查询结果,避免重复执行,比 JOIN 更可控
- 若硬要 JOIN,必须用
LEFT JOIN后加WHERE ... IS NOT NULL模拟EXISTS,但可读性和维护性下降
推荐写法(非强制 JOIN):
SELECT u.id, u.name FROM user u WHERE EXISTS ( SELECT 1 FROM `order` o WHERE o.user_id = u.id AND o.amount > 100 );
IN/JOIN 改写后务必用 EXPLAIN 验证执行计划
改写不是终点,EXPLAIN 才是判断是否真优化的关键。常见陷阱包括:
- 没建对索引:比如只建了
order(user_id),但WHERE status = 'paid'仍走全表扫描 - JOIN 顺序错误:MySQL 5.7 默认使用嵌套循环,小表在前才能减少驱动次数;可用
STRAIGHT_JOIN强制顺序(慎用) - 隐式类型转换:如
user.id是BIGINT,而order.user_id是VARCHAR,会导致索引失效
执行前必查:
EXPLAIN SELECT DISTINCT u.id, u.name FROM user u INNER JOIN `order` o ON u.id = o.user_id WHERE o.status = 'paid';
重点关注 type 是否为 ref 或 range,key 是否命中联合索引,rows 是否显著下降。
真正卡住性能的,往往不是 JOIN 写法本身,而是索引缺失、数据倾斜、或误以为“用了 JOIN 就一定快”。每次改写后,拿 EXPLAIN 和实际执行时间说话,比任何模板都管用。










