PostgreSQL 更新加入与 SQL Server 更新加入

Cer*_*vEd 5 postgresql join sql-server update

我最近开始将个人项目从 Microsoft SQL Server 转换为 PostgreSQL,我对UPDATE JOIN在两个表之间执行时遇到的糟糕性能感到惊讶。

假设它们看起来像:

CREATE TABLE foo (
  id INTEGER NOT NULL PRIMARY KEY,
  bar INTEGER NULL
);

CREATE TABLE foo2 (
  id INTEGER NOT NULL PRIMARY KEY,
  bar INTEGER NULL
);
Run Code Online (Sandbox Code Playgroud)

在 T-SQL 中,我会使用这样的连接来进行更新:

UPDATE foo
SET bar = t2.bar
FROM foo t1
JOIN foo2 t2
ON t1.id = t2.id;
Run Code Online (Sandbox Code Playgroud)

但是在 Postgres 中运行,查询速度非常慢。

如果我将其更改为:

UPDATE foo
SET bar = t2.bar
FROM foo2 t2
WHERE foo.id = t2.id;
Run Code Online (Sandbox Code Playgroud)

这不是问题。

我知道语法是不同的,但我希望查询优化器能在同一个球场上解决一些问题。相反,事情变得疯狂。除了语法差异之外,我看不到的两个查询之间是否存在细微差别?

解释计划

Update on foo  (cost=85852.43..6211995294.24 rows=338326628280 width=1027)
  ->  Nested Loop  (cost=85852.43..6211995294.24 rows=338326628280 width=1027)
        ->  Seq Scan on foo  (cost=0.00..145721.10 rows=582410 width=1010)
        ->  Materialize  (cost=85852.43..247935.91 rows=580908 width=17)
              ->  Hash Join  (cost=85852.43..241627.37 rows=580908 width=17)
                    Hash Cond: (t1.id = t2.id)
                    ->  Seq Scan on foo t1  (cost=0.00..145721.10 rows=582410 width=10)
                    ->  Hash  (cost=75754.08..75754.08 rows=580908 width=15)
                          ->  Seq Scan on foo2 t2  (cost=0.00..75754.08 rows=580908 width=15)
Run Code Online (Sandbox Code Playgroud)
Update on foo (cost=87575.47..535974.25 rows=581621 width=1022)
  ->  Hash Join  (cost=87575.47..535974.25 rows=581621 width=1022)
        Hash Cond: (foo.id = t2.id)
        ->  Seq Scan on foo (cost=0.00..151301.17 rows=1140417 width=1011)
        ->  Hash  (cost=75761.21..75761.21 rows=581621 width=36)
              ->  Seq Scan on foo2 t2  (cost=0.00..75761.21 rows=581621 width=36)
Run Code Online (Sandbox Code Playgroud)

Erw*_*ter 10

但是在 Postgres 中运行,查询速度非常慢。

UPDATE foo
SET    bar = t2.bar
FROM   foo t1
JOIN   foo2 t2 ON t1.id = t2.id;
Run Code Online (Sandbox Code Playgroud)

foo和之间没有连接条件t1,隐式CROSS JOIN强制笛卡尔积,即O(N²)(!)更新操作而不是仅仅O(N)。结果是不确定的废话。查询计划中的效果也很明显:rows=338326628280而不是rows=581621(另外:两个计划都是从稍微不同的表中生成的,但这似乎与问题无关。)

可以通过添加连接条件来修复,例如:

UPDATE foo
SET    bar = t2.bar
FROM   foo t1
JOIN   foo2 t2 ON t1.id = t2.id
WHERE  foo.id = t1.id;  -- !
Run Code Online (Sandbox Code Playgroud)

好吧,从技术上讲,这是一个WHERE条件,但都是一样的。

但这只是给猪涂口红。虽然id是每个表的 PK 列,但这只是增加了噪音。使用您已经找到的命令:

UPDATE foo
SET    bar = t2.bar
FROM   foo2 t2
WHERE  foo.id = t2.id;
Run Code Online (Sandbox Code Playgroud)

该手册建议使用以下FROM条款UPDATE

不要将目标表作为 a 重复,from_item除非您打算进行自联接(在这种情况下,它必须以别名出现在 中from_item)。

和:

当存在FROM子句时,本质上发生的是目标表与from_item列表中提到的表连接,连接的每个输出行代表目标表的更新操作。使用时,FROM您应该确保连接最多为要修改的每一行生成一个输出行。换句话说,目标行不应连接到其他表中的多行。如果是这样,则只有一个连接行将用于更新目标行,但将使用哪一个是不容易预测的。

如果您需要一个LEFT [OUTER] JOIN额外的表,这样的自连接是有意义的(甚至是必要的!)。可悲的是,SQL 中没有规定要"FROM LEFT"UPDATE. 例子:

  • 在 SQL Server 中,如果 `FROM` 包含 `foo` 和 `UPDATE` 引用 `foo`,则它们都是对同一个实例的引用。出于这个原因,如果你想绝对明确,你也可以通过它在 `FROM` 中的别名在 `UPDATE` 子句中引用你的目标表,这样你就可以执行 `UPDATE t1 SET ... FROM foo AS t1 INNER加入...`。我实际上认为 PostgreSQL 也支持这一点,但显然我错了。 (3认同)