0Pricing
SQL Academy · 课时

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 反馈 — 无需本地设置。

此课程中的所有课时

  1. 相关子查询
  2. EXISTS 与 NOT EXISTS
  3. IN、ANY 与 ALL 对比
  4. EXISTS 与 JOIN 的性能对比
← 返回 SQL Academy