使用子查询更新与使用连接更新 - 性能更好

Posted

技术标签:

【中文标题】使用子查询更新与使用连接更新 - 性能更好【英文标题】:Update with subquery vs Update with join - Which is better in performance 【发布时间】:2014-01-27 11:57:03 【问题描述】:

Postgres 数据库中,以下两个查询在 性能 方面是否等效?每个人都说“连接总是比子查询快”,但是 Postgres 查询计划器是否在幕后将子查询优化为连接?

查询 1:

UPDATE table_a
SET col_1 = 'a fixed value'
WHERE col_2 IN (
    SELECT col_2 FROM table_b
);

解释计划:

更新 table_a(成本=0.00..9316.10 行=1 宽度=827) -> 嵌套循环半连接(成本=0.00..9316.10 行=1 宽度=827) -> table_a 上的 Seq Scan(成本=0.00..9287.20 行=1 宽度=821) -> 在 table_b 上使用 idx_table_b 进行索引扫描(成本=0.00..14.45 行=1 宽度=14) 索引条件:(col_2 = (table_a.col_2)::numeric)

查询 2:

UPDATE table_a ta
SET col_1 = 'a fixed value'
FROM table_b tb
WHERE ta.col_2 = tb.col_2;

解释计划:

更新 table_ata(成本=0.00..9301.67 行=1 宽度=827) -> 嵌套循环(成本=0.00..9301.67 行=1 宽度=827) -> table_ata 上的 Seq 扫描(成本=0.00..9287.20 行=1 宽度=821) -> 在 table_b tb 上使用 idx_table_b 进行索引扫描(成本=0.00..14.45 行=1 宽度=14) 索引条件:(col_2 = (ta.col_2)::numeric)

我相信它们在结果上是等效的(如果我错了,请提醒我)。我用大量数据尝试了几个解释计划。在更新完整表和将 table_a.col_2 限制为一个小子集时,它们的性能似乎相当。

我想确定我没有遗漏任何东西。如果它们是等价的,你会选择哪一个?为什么?

【问题讨论】:

它们的功能是否相同?在第一种情况下,只会更新匹配的行,其他行将保留它们的值。在第二种情况下,不匹配的行将变为NULL @hashbrown 不正确。我刚刚测试过 - 不匹配的行将不会变成NULL。您一定对第三种可能的构造感到困惑:UPDATE table_a SET col_1 = (SELECT 'a fixed value' FROM table_b where table_a.col_2 = table_b.col_2)。在这个构造中,是的,不匹配的行变成NULL 并且 col_2必须是唯一的。 如果您一直在查看解释输出,请包含它!此外,提供有用的表定义和虚拟数据来匹配您的查询,这样我们就不必编造这些东西了。 【参考方案1】:

Postgres 查询规划器是否在后台将子查询优化为连接?

通常是的。

别猜了,看看查询计划。

给定:

CREATE TABLE table_a(col_1 文本,col_2 整数); CREATE TABLE table_b(col_2 整数); 插入表_b(col_2)值(1),(2),(4),(NULL); INSERT INTO table_a (col_1, col_2) VALUES ('a fixed value', 2), ('a fixed value', NULL), ('some other value', 2); 分析表_a; 分析表_b;

比较:

regress=> 解释 UPDATE table_a SET col_1 = '一个固定值' 在哪里 col_2 ( 从 table_b 中选择 col_2 ); 查询计划 -------------------------------------------------- ---------------------- 更新 table_a(成本=1.09..2.15 行=2 宽度=16) -> 哈希半连接(成本=1.09..2.15 行=2 宽度=16) 哈希条件:(table_a.col_2 = table_b.col_2) -> table_a 上的 Seq Scan(成本=0.00..1.03 行=3 宽度=10) -> 哈希(成本=1.04..1.04 行=4 宽度=10) -> table_b 上的 Seq Scan(成本=0.00..1.04 行=4 宽度=10) (6 行) regress=> 解释 UPDATE table_ata regress-> SET col_1 = '一个固定值' 回归-> FROM table_b tb 回归-> 哪里 ta.col_2 = tb.col_2; 查询计划 -------------------------------------------------- -------------------------- 更新 table_ata(成本=1.07..2.14 行=1 宽度=16) -> 哈希连接(成本=1.07..2.14 行=1 宽度=16) 哈希条件:(tb.col_2 = ta.col_2) -> table_b tb 上的 Seq Scan(成本=0.00..1.04 行=4 宽度=10) -> 哈希(成本=1.03..1.03 行=3 宽度=10) -> table_ata 上的 Seq Scan(成本=0.00..1.03 行=3 宽度=10) (6 行)

看到了吗?一样的计划。子查询已转换为连接。

通常用EXISTSIN 更简洁。这对于NOT INNOT EXISTS 来说更为重要,它们在面对空值时在语义上是不同的,所以无论如何这是一个好习惯。你会写:

UPDATE table_a a
SET col_1 = 'a fixed value'
WHERE EXISTS (SELECT 1 FROM table_b b WHERE b.col_2 = a.col_2);

这将倾向于产生相同的计划,但它更好一点 IMO - 尤其是因为如果它计划到一个连接,一个相关的子查询通常没有巨大的IN 列表扫描那么可怕。

【讨论】:

是的,解释输出与 Q1Update > Nested Loop Semi Join > Seq Scan > Index ScanQ2Update > Nested Loop > Seq Scan > Index Scan 非常相似。我不确定Semi Join 是什么,但这是唯一的区别。我将使用文本输出更新问题。 超级迟到的回复,但对于以后阅读本文的任何人来说,Semi Join 就像一个只寻找是否匹配的连接 - 它在第一次匹配后返回,而不是合并所有匹配像普通的Join 这样的记录。因此,如果有多个匹配项,它不会复制您真正关心的记录。【参考方案2】:

输入:

如果指定的值与子查询或列表中的任何值匹配,则返回 true。

存在:

如果子查询包含任何行,则返回 true。

加入:

在连接列上连接 2 个结果集。


如果加入列是UNIQUE,那么join 会更快。

如果不是,那么IN is faster than JOIN on DISTINCT

有关 Microsoft SQL Server 的性能详细信息,请参阅我的博客中的这篇文章,这可能也与 PostgreSQL 相关:

IN vs. JOIN vs. EXISTS in Microsoft SQL Server

【讨论】:

感谢您的文章。 PostgreSQL 查询计划器的行为可能会有所不同,这就是我想知道的。 那不是最相关的文章,因为它是针对完全不同的数据库引擎的。当您关注sql 标签时,请记住它的意思是“结构化查询语言(SQL)”,而不是“Microsoft SQL Server”,并相应地回答。

以上是关于使用子查询更新与使用连接更新 - 性能更好的主要内容,如果未能解决你的问题,请参考以下文章

子查询与连接

带有子查询的 Oracle 更新 - 性能问题

使用子查询改进 MySql 查询左外连接

访问:使用子查询中的计数更新查询 - 错误或所有结果

MySQL--5子查询与连接小结

SQL 连接与 SQL 子查询(性能)?