没有绝对更快的写法,只有更匹配当前数据结构和查询意图的方式。子查询和JOIN在不同场景下性能差异明显,关键看表大小、索引情况、MySQL版本以及是否为相关子查询。
小结果集 + 有索引时,IN子查询常更快
当子查询返回少量主键(比如几十到几百条),且被查字段已建索引,MySQL 5.6+ 会自动将 IN 子查询优化为半连接(Semi-Join)或 LooseScan,跳过全表扫描。例如:
SELECT * FROM users WHERE id IN (SELECT user_id FROM orders WHERE status = 'paid') 若orders.user_id有索引,执行计划中
type: index或
range,实际只扫索引页,不回表 而对应 JOIN 写法若驱动表选错(如用大表 orders 驱动 users),反而触发全表扫描
大数据量 + 关联条件复杂时,JOIN 更可控
INNER JOIN 由优化器自动选择小表为驱动表,配合被驱动表上的索引,能稳定走嵌套循环(Nested Loop)。但要注意:
必须确保 ON 字段在被驱动表上有索引,否则性能断崖式下跌 LEFT JOIN 固定左表为驱动表,若左表很大且右表无索引,代价极高 EXPLAIN 中出现 DEPENDENT SUBQUERY 是危险信号——意味着外层每行都执行一次子查询,百万数据≈百万次查询语义明确、只需判断存在性时,优先用 EXISTS
相比 IN,EXISTS 在子查询找到第一条匹配就终止,不加载全部结果,内存开销低,特别适合“是否存在”的逻辑:
WHERE EXISTS (SELECT 1 FROM logs l WHERE l.user_id = u.id AND l.action = 'login') 即使 logs 表有千万行,只要user_id有索引,单次判断极快 IN 在子查询结果多时需加载并去重,可能触发临时表或内存溢出
版本与写法细节影响巨大
MySQL 5.6 之前,IN 子查询基本是嵌套执行;5.6+ 启用 Semi-Join 优化后,很多场景反超 JOIN。但以下情况仍倾向 JOIN:
需要返回多列关联字段(如同时取 order.date 和 product.name) 涉及 GROUP BY 或聚合后再过滤(子查询无法直接参与分组) UPDATE/DELETE 中的子查询受限较多,JOIN 写法更直观可靠 使用 STRAIGHT_JOIN 强制驱动顺序,可规避优化器误判