explain显示rows爆炸式增长且type=all,基本可断定触发隐式笛卡尔积;识别方法包括format=tree中多表无驱动顺序、dependent subquery配合外层大rows、show profile中statistics/executing阶段耗时占比极高。

EXPLAIN 显示 rows 值爆炸式增长,且 type=ALL 出现在子查询或 JOIN 内表中,基本可以断定是嵌套查询触发了隐式笛卡尔积——这不是“写法看起来复杂”,而是执行计划实际在做 N×M 行级暴力匹配。
怎么一眼识别笛卡尔积正在发生
-
EXPLAIN FORMAT=TREE中出现未指定驱动顺序的多表组合,或子查询标记为DEPENDENT SUBQUERY且外层rows较大 - 外层表返回 1000 行,内层子查询
rows是 50 万,但两者无有效关联条件(比如漏写WHERE a.id = b.user_id),最终估算行数变成 1000 × 50 万 = 5 亿 -
SHOW PROFILE FOR QUERY N中statistics和executing阶段耗时占比极高,而copying to tmp table或Sorting result反倒不突出——说明 CPU 正卡在“比对”本身,不是后续加工
为什么没写 CROSS JOIN 也会出笛卡尔积
MySQL 不会主动报错或拒绝执行缺少连接条件的多表查询。只要 FROM t1, t2 或 LEFT JOIN t2 ON 1=1 这类写法存在,优化器就可能放弃推导关联逻辑,直接按乘积展开:
- 多个子查询并列在
SELECT列表中,如SELECT id, (SELECT COUNT(*) FROM log WHERE user_id = u.id), (SELECT MAX(time) FROM audit WHERE user_id = u.id) FROM user u:每个子查询都独立执行,且无法共享中间结果 -
IN子查询中用了非确定性函数,如WHERE id IN (SELECT id FROM t WHERE created_at > NOW() - INTERVAL 1 DAY):每次外层行触发时,NOW()重求值,导致子查询无法缓存,也无法转成 semi-join -
JOIN的ON条件里混用函数或隐式转换,例如ON UPPER(a.name) = UPPER(b.name)或ON a.code = b.code_str(前者是CHAR,后者是VARCHAR):索引失效,优化器退回到嵌套循环 + 全表扫描组合
如何避免执行时生成笛卡尔积
- 所有
JOIN必须带明确、可走索引的等值条件,禁止ON 1=1、ON TRUE或空ON - 把相关子查询改写为
LEFT JOIN+GROUP BY,尤其当子查询含聚合时:(SELECT SUM(amount) FROM orders o2 WHERE o2.user_id = u.id)→ 改成显式LEFT JOIN (SELECT user_id, SUM(amount) s FROM orders GROUP BY user_id) o2 ON u.id = o2.user_id - 对高频使用的子查询结果,提前物化到临时表并加索引,而不是每次现场算:
CREATE TEMPORARY TABLE tmp_active_users AS SELECT id FROM users WHERE status = 'active'; ALTER TABLE tmp_active_users ADD PRIMARY KEY (id); - 检查
EXPLAIN中每张表的key是否非NULL,rows是否合理;若某张表rows是总行数的 80% 以上,且filtered
真正危险的不是“写了嵌套”,而是嵌套后执行计划完全失控——它不会报错,也不会慢得明显(初期数据少时一切正常),但一旦某天用户表从 10 万涨到 300 万,CPU 就会在你毫无察觉时突然拉满。










