EXISTS 与 JOIN 的性能对比
选择更快的模式
EXISTS 与 JOIN 的性能对比 是 CoddyKit 上的免费 SQL Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 SQL Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 SQL Academy 课程共包含 4 节课。
为什么这里需要关注性能
当您需要检查另一个表中是否存在相关行时,SQL 提供了多种工具:EXISTS、IN 和 JOIN。它们都能产生正确结果,但具体性能可能大不相同,这取决于数据量、索引和数据库引擎。
在本课中,您将了解每种方法在底层的工作方式,以及应在何时选择哪一种。
示例表
在整个课程中,我们将使用两个表:customers 和 orders。一个客户可能有零个或多个订单。这是一种经典的一对多关系,非常适合测试 EXISTS 与 JOIN 模式。
CREATE TABLE customers (
id SERIAL PRIMARY KEY,
name VARCHAR(100)
);
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
customer_id INT REFERENCES customers(id),
total NUMERIC(10,2)
);
INSERT INTO customers (name) VALUES
('Alice'), ('Bob'), ('Carol'), ('Dave');
INSERT INTO orders (customer_id, total) VALUES
(1, 120.00), (1, 85.50), (3, 200.00);JOIN 方法
一种常见模式是使用 INNER JOIN 查找至少有一个订单的客户。这种方法可行,但请注意其中的问题:如果一个客户有五个订单,那么在 DISTINCT 对结果去重之前,该客户会在结果集中出现五次。
这种重复会增加数据库的工作量——数据库必须先构建完整的连接结果,然后再去重。
SELECT DISTINCT c.id, c.name
FROM customers c
INNER JOIN orders o ON o.customer_id = c.id;EXISTS 方法
EXISTS 回答的是一个是非问题:是否存在至少一行匹配的行?引擎找到第一个匹配项后就会停止扫描——这称为短路求值。
它不会产生重复行,也不需要使用 DISTINCT,因为 EXISTS 实际上从不返回内部行。
SELECT c.id, c.name
FROM customers c
WHERE EXISTS (
SELECT 1
FROM orders o
WHERE o.customer_id = c.id
);关键在于短路求值
短路求值意味着子查询在找到一行符合条件的行后就会停止。无论客户有 1 个订单还是 10,000 个订单,EXISTS 都只会读取到第一个匹配项为止。
JOIN 必须读取所有匹配行来构建结果集,即使您只关心是否存在匹配项也是如此。对于列较多且每个父行有许多子行的表,这种差异会迅速累积。
-- EXISTS stops after finding row #1
SELECT c.name
FROM customers c
WHERE EXISTS (
SELECT 1 -- 'SELECT 1' is conventional; the value does not matter
FROM orders o
WHERE o.customer_id = c.id
);
-- JOIN scans ALL matching order rows
SELECT DISTINCT c.name
FROM customers c
INNER JOIN orders o ON o.customer_id = c.id;NOT EXISTS 与 LEFT JOIN ... IS NULL
对于相反的检查——查找没有订单的客户——您可以使用 NOT EXISTS,也可以使用 LEFT JOIN ... WHERE IS NULL 模式。两种方式都很常见,但 NOT EXISTS 通常更易读,优化器也经常更偏好它。
-- NOT EXISTS
SELECT c.id, c.name
FROM customers c
WHERE NOT EXISTS (
SELECT 1
FROM orders o
WHERE o.customer_id = c.id
);
-- LEFT JOIN ... IS NULL (equivalent result)
SELECT c.id, c.name
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
WHERE o.id IS NULL;索引的作用
在外键列上建立索引,可以让 EXISTS 和 JOIN 都获得极大的性能提升。如果没有在 orders.customer_id 上建立索引,外层的每一行都会触发对订单表的全表扫描。
添加该索引通常是提升性能最明显的单项措施,其影响往往大于在 EXISTS 和 JOIN 之间进行选择。
-- Create an index on the foreign key
CREATE INDEX idx_orders_customer_id ON orders(customer_id);
-- Now both patterns use an index lookup instead of a full scan
EXPLAIN
SELECT c.name
FROM customers c
WHERE EXISTS (
SELECT 1 FROM orders o
WHERE o.customer_id = c.id
);读取 EXPLAIN 输出
使用 EXPLAIN(或者使用 EXPLAIN ANALYZE 同时运行查询)来查看数据库如何执行查询。请关注以下线索:
- 索引扫描——这是好现象,说明正在使用索引。
- 大型表上的顺序扫描——可能是一个危险信号,使用索引或许会有所帮助。
- 哈希连接 / 嵌套循环——这是所选择的连接算法;嵌套循环与索引扫描配合效果良好。
EXPLAIN ANALYZE
SELECT c.name
FROM customers c
INNER JOIN orders o ON o.customer_id = c.id
GROUP BY c.id, c.name
HAVING COUNT(o.id) > 0;JOIN 何时更有优势
EXISTS 非常适合单纯检查是否存在。但如果您还需要相关表中的数据,例如订单总额或订单日期,就必须使用 JOIN。您无法从 EXISTS 子查询内部返回列。
请选择适合问题的工具:EXISTS 用于“是否存在?”,JOIN 用于“返回两个表中的数据”。
-- Need order data? JOIN is the only option.
SELECT c.name, o.total, o.id AS order_id
FROM customers c
INNER JOIN orders o ON o.customer_id = c.id
ORDER BY c.name;大型数据集中的 IN 与 EXISTS 对比
IN (subquery) 会先计算整个子查询,构建一个内存中的值列表,然后将外层的每一行与该列表进行比较。当行数达到数百万时,这个列表可能耗尽内存。
EXISTS 会逐行计算并在找到匹配项后立即停止,因此不会将完整的内部结果集全部物化。对于大型相关检查,EXISTS 几乎总是比 IN 更快。
-- IN builds the full list first
SELECT name
FROM customers
WHERE id IN (
SELECT customer_id FROM orders
);
-- EXISTS evaluates per-row and short-circuits
SELECT name
FROM customers c
WHERE EXISTS (
SELECT 1 FROM orders o
WHERE o.customer_id = c.id
);决策速查表
下面是选择合适模式的快速参考:
- EXISTS — 您只需要知道是否存在匹配项;适用于大型子表;反连接使用 NOT EXISTS。
- JOIN — 您需要相关表中的列,或需要跨两个表进行聚合。
- IN — 适用于简短的静态值列表(
WHERE status IN ('active', 'pending'));避免用于大型子查询。 - 始终为外键列建立索引 — 这比语法选择更加重要。
快速检查
下面哪条陈述最能解释:在检查相关行是否存在时,为什么 EXISTS 可能比 INNER JOIN + DISTINCT 更快?
课程回顾
在本课中,您学习了如何在注重性能的 SQL 中选择 EXISTS 或 JOIN:
- EXISTS 会提前停止 — 找到第一个匹配项后就停止扫描,无需使用 DISTINCT 便可避免重复项。
- JOIN 会返回所有匹配的行 — 当您需要相关表中的数据时使用它;但如果您只关注父行,请添加 DISTINCT 或 GROUP BY。
- NOT EXISTS 是一种简洁的反连接模式;LEFT JOIN ... IS NULL 与它等价,但写法更冗长。
- 避免对大型子查询使用 IN — 它会物化整个内部结果;EXISTS 的内存效率更高。
- 为外键建立索引 — 无论选择哪种语法,这一步通常都能带来最大的性能提升。
- 使用 EXPLAIN / EXPLAIN ANALYZE 验证执行计划,并确认正在使用索引。
常见问题解答
「EXISTS 与 JOIN 的性能对比」课时是免费的吗?
是的 — 「EXISTS 与 JOIN 的性能对比」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 SQL Academy 课程的其余内容,请升级到 CoddyKit PRO。 SQL Academy 课程共包含 4 节课。
「EXISTS 与 JOIN 的性能对比」这节课中我会学到什么?
选择更快的模式 你通过在浏览器中直接运行的动手代码来练习 SQL Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 SQL Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 SQL Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。
「EXISTS 与 JOIN 的性能对比」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 SQL Academy 课中编写并运行代码吗?
能。每节 SQL Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- 相关子查询
- EXISTS 与 NOT EXISTS
- IN、ANY 与 ALL 对比
- EXISTS 与 JOIN 的性能对比