子查询未写关联条件会导致笛卡尔积式爆炸,如外层1000行×子查询500行=50万行;需确保in/exists/join中含显式关联条件(如sub_table.x = outer_table.y),并用explain检查rows是否异常膨胀。

子查询没写关联条件,直接爆炸
嵌套查询里子查询和外层表之间没用 WHERE 或 ON 绑定字段,数据库就把子查询结果当静态集合,跟外层每一行硬组合。比如外层 1000 行 × 子查询 500 行 = 50 万行,而你只想要匹配的几十行。
- 检查所有
IN、=、JOIN后的子查询,确认是否含WHERE sub_table.x = outer_table.y这类显式引用 - 老式写法
FROM a, (SELECT * FROM b) c必须在后续WHERE里补全a.id = c.a_id,缺一个就炸 -
EXPLAIN看rows列:如果某张表显示 500000 行,但实际只有 1000 行,基本就是这问题
用 IN 做子查询反而更慢
MySQL 5.7 及更早版本对 WHERE x IN (SELECT y FROM t) 的优化很弱——子查询返回空、含 NULL、或外层有多个可匹配字段时,优化器大概率放弃半连接,退化成嵌套循环+全表扫描,效果等于手写笛卡尔积。
- 优先改用
WHERE EXISTS (SELECT 1 FROM t WHERE t.y = outer.x),语义明确,MySQL 对它的执行路径选择更稳定 - 子查询里别写
SELECT *,只查必要字段;要去重就显式写SELECT DISTINCT y,否则某些版本会多加一层临时表 - PostgreSQL 对
IN优化较好,但 MySQL 在子查询结果超 1000 行时,大概率放弃哈希半连接,走慢路径
LEFT JOIN 套子查询后 COUNT(*) 失真
典型错误:SELECT COUNT(*) FROM a LEFT JOIN (SELECT * FROM b WHERE cond) c ON a.id = c.a_id。本意是统计 a 表总行数,但子查询为空时 COUNT(*) 仍返回 a 行数;若子查询因 c.a_id 不唯一导致重复匹配,COUNT(*) 就会大于 COUNT(a.id),结果完全不可信。
- 统计主表数量,直接用
COUNT(a.id)或COUNT(1),别依赖COUNT(*)在JOIN后的结果 - 子查询若需过滤,把
WHERE cond写进子查询内部,而不是留到外层再ON或WHERE - 检查子查询中用于
ON的字段(如c.a_id)是否有索引;没有的话,关联可能无法驱动高效查找,间接放大中间结果集
子查询带 GROUP BY 或聚合后,外层不能直接当原表用
子查询一旦用了 GROUP BY、SUM、COUNT 等,输出结构就不再是原始表,外层不能再假设用 id 直接对齐。比如 (SELECT order_id, SUM(amount) FROM order_items GROUP BY order_id) 返回的是聚合后的宽表,不是明细行集合。
- 外层关联时,必须用
WHERE b.order_id IN (SELECT order_id FROM ...)或EXISTS,而不是JOIN后按order_id直接连 - 一对多关系下,优先在子查询里先聚合,避免扩散;需要明细但只取最新一条?用
ROW_NUMBER() OVER (PARTITION BY order_id ORDER BY created_at DESC)筛 - MySQL 8.0.14+ 或 PostgreSQL 可考虑
LATERAL,让子查询能引用外层变量,避免提前物化整个结果集
实际写的时候,最容易被忽略的是子查询里字段类型和外层不一致——比如 t1.id 是 BIGINT,t2.t1_id 是 VARCHAR,即使值看起来一样,也会触发隐式转换,索引失效,最终退化为全表扫描+嵌套循环。










