使用子查询更新与使用连接更新 - 性能更好
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 行)看到了吗?一样的计划。子查询已转换为连接。
通常用EXISTS
比IN
更简洁。这对于NOT IN
和NOT 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
列表扫描那么可怕。
【讨论】:
是的,解释输出与 Q1 的Update > Nested Loop Semi Join > Seq Scan > Index Scan
和 Q2 的 Update > 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”,并相应地回答。以上是关于使用子查询更新与使用连接更新 - 性能更好的主要内容,如果未能解决你的问题,请参考以下文章