直接看执行计划最准——只要子查询没加WHERE、JOIN条件,又没用EXISTS或IN做半连接,就极大概率产生笛卡尔积;EXPLAIN中rows异常大、type为ALL且无key、子查询显示DEPENDENT SUBQUERY却未引用外层字段,均为明确信号。

怎么看子查询里有没有笛卡尔积
直接看执行计划最准——只要子查询没加 WHERE、JOIN 条件,又没用 EXISTS 或 IN 做半连接,就极大概率产生笛卡尔积。它不会报错,但行数会爆炸式增长,查着查着就卡死。
EXPLAIN 里哪些信号说明出问题了
运行 EXPLAIN(MySQL)或 EXPLAIN ANALYZE(PostgreSQL),重点盯这几个地方:
-
rows列数值异常大(比如两个 1000 行的表,乘出来就是百万级) - 出现
ALL类型扫描(没走索引)且没key或possible_keys - 子查询部分显示
DEPENDENT SUBQUERY但内部没关联字段(比如外层t1.id没在子查询WHERE里引用) - PostgreSQL 中出现
Nested Loop且左/右节点的Rows Removed by Filter占比极高(99%+),说明大量中间结果被丢弃
常见踩坑的子查询写法
这些写法看着合法,实则暗藏笛卡尔积风险:
- 把相关子查询写成非相关子查询:
(SELECT name FROM users)放在SELECT列表里,没加WHERE id = t.user_id - 用
=比较单值子查询,但子查询返回多行(此时会报错,但若用IN或没校验就容易漏) - 在
FROM子句中写无关联子查询:SELECT * FROM orders, (SELECT * FROM products)—— 这是显式笛卡尔积 - LEFT JOIN 后跟无条件子查询:
LEFT JOIN (SELECT max(price) FROM items) AS p ON true,相当于给每行都拼上全部子查询结果
怎么快速验证是否真有笛卡尔积
别等上线才发现,本地加个简单校验:
- 先跑
SELECT COUNT(*)的子查询本身,再跑完整 SQL 的COUNT(*),如果后者远大于前者 × 外层行数(比如外层 100 行,子查询 50 行,但总数是 20000),基本坐实 - 把子查询单独拎出来,加
LIMIT 5执行,看是否真只返回预期的 1 行或少量行;如果返回几十上百行,而外层又有几百行,乘积就危险了 - 临时把子查询改成
SELECT 1,对比执行时间:如果原 SQL 耗时几秒,改完秒出,说明子查询本身被反复执行且结果膨胀
笛卡尔积最难察觉的地方在于:它不报错、不告警,只悄悄拖慢系统、吃光内存。越是在复杂嵌套或动态拼接 SQL 里,越要手动核对每一层子查询的驱动关系和输出规模。

















