












一句话概括:EXISTS 只判断“有没有匹配的行”,不关心匹配了几行,也不关心匹配行里有什么。
它的逻辑语义是:对左表的每一行,拿关联字段去子查询里找,只要能找到至少一条满足条件的记录,这行就保留;一条都找不到,就丢弃。
这决定了它两个关键特性:
天然去重:右表匹配多条,也只算“有”,不会让左表行数膨胀。
找到即停:匹配到第一条就停止扫描,不关心右表还有多少条。
两张表:
订单表 orders
退款表 xq_order_refund
注意:A001 有 2 条退款记录(一条待处理、一条已处理),A002 只有已处理,A003 有待处理。
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
);
结果:
A001 只出现 1 次,因为 EXISTS 只判断“有没有”,不关心有几条。
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)。
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 的核心是存在性判断,天然去重、找到即停。
它和 LEFT JOIN 不等价:LEFT JOIN 会膨胀行数,EXISTS 不会。
只判断“有没有”时,EXISTS 比 LEFT JOIN 更自然、通常也更高效。
索引是前提:没有索引,换任何写法都救不了。
写 EXISTS 时,子查询的 WHERE 条件必须和外表关联,否则结果会全错。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。