别再乱用 LEFT JOIN 了:EXISTS 的正确理解与实战对比
一、EXISTS 到底在做什么?
一句话概括:EXISTS 只判断“有没有匹配的行”,不关心匹配了几行,也不关心匹配行里有什么。
它的逻辑语义是:对左表的每一行,拿关联字段去子查询里找,只要能找到至少一条满足条件的记录,这行就保留;一条都找不到,就丢弃。
这决定了它两个关键特性:
-
天然去重:右表匹配多条,也只算“有”,不会让左表行数膨胀。
-
找到即停:匹配到第一条就停止扫描,不关心右表还有多少条。
二、示例场景
两张表:
订单表 orders
| order_no | tenant_id | amount |
|---|---|---|
| A001 | 1 | 100 |
| A002 | 1 | 200 |
| A003 | 2 | 150 |
退款表 xq_order_refund
| order_no | tenant_id | status |
|---|---|---|
| A001 | 1 | 0 |
| A001 | 1 | 1 |
| A002 | 1 | 1 |
| A003 | 2 | 0 |
注意:A001 有 2 条退款记录(一条待处理、一条已处理),A002 只有已处理,A003 有待处理。
三、查“有待处理退款的订单”
写法一:EXISTS
SELECT o.order_no, o.amount
FROM orders o
WHERE EXISTS (
SELECT 1
FROM xq_order_refund r
WHERE r.order_no = o.order_no
AND r.tenant_id = o.tenant_id
AND r.status = 0
);
结果:
| order_no | amount |
|---|---|
| A001 | 100 |
| A003 | 150 |
A001 只出现 1 次,因为 EXISTS 只判断“有没有”,不关心有几条。
写法二:LEFT JOIN
SELECT DISTINCT o.order_no, o.amount
FROM orders o
LEFT JOIN xq_order_refund r
ON r.order_no = o.order_no
AND r.tenant_id = o.tenant_id
AND r.status = 0
WHERE r.order_no IS NOT NULL;
结果和 EXISTS 一样,但必须加 DISTINCT。如果不加,A001 会因为退款表有 2 条记录而出现 2 次(一条匹配 status=0,一条匹配为 NULL)。
四、核心差异对比
| 维度 | EXISTS | LEFT JOIN |
|---|---|---|
| 逻辑语义 | 判断“有没有匹配” | 左表全保留,右表匹配就展开 |
| 匹配多条时 | 只算“有”,不膨胀 | 展开成多行,行数膨胀 |
| 是否需要 DISTINCT | 不需要,天然去重 | 通常需要 |
| 能否取右表列 | 不能 | 可以 |
| 找到匹配后 | 立即停止 | 可能继续扫描 |
| 可读性 | 意图直接:找存在性 | 需要额外条件才等价 |
五、SELECT 1 怎么理解?
EXISTS (SELECT 1 FROM ... WHERE ...)
SELECT 1 只是一个占位符。EXISTS 不关心 SELECT 后面写什么,写 1、*、id 都完全等价,性能没有任何区别。
写成 SELECT 1 是约定俗成的习惯,目的是告诉读代码的人:我不需要任何列,只判断存在性。
六、效率关键:索引
EXISTS 快不快,第一决定因素是索引,不是写法本身。
理想情况:在 xq_order_refund 上建联合索引
CREATE INDEX idx_refund_order_tenant_status
ON xq_order_refund (order_no, tenant_id, status);
对每行订单做一次索引精确查找,找到即停,复杂度接近 O(N × logM),效率很高。
糟糕情况:没有索引,每行订单都全表扫描退款表,复杂度退化成 O(N × M),数据量大时会非常慢。
七、什么时候该用 EXISTS?
| 场景 | 推荐写法 |
|---|---|
| 只筛“有匹配”的订单,不需要退款表列 | EXISTS |
| 只筛“无匹配”的订单 | NOT EXISTS |
| 需要退款表的具体列(金额、时间等) | LEFT JOIN |
| 要保留所有订单,并标记是否有退款 | LEFT JOIN + 去重子查询 |
八、总结
-
EXISTS 的核心是存在性判断,天然去重、找到即停。
-
它和 LEFT JOIN 不等价:LEFT JOIN 会膨胀行数,EXISTS 不会。
-
只判断“有没有”时,EXISTS 比 LEFT JOIN 更自然、通常也更高效。
-
索引是前提:没有索引,换任何写法都救不了。
-
写 EXISTS 时,子查询的 WHERE 条件必须和外表关联,否则结果会全错。

浙公网安备 33010602011771号