用相同的值更新一行是否实际上会更新该行?


28

我有一个与性能有关的问题。假设我有一个名字为Michael的用户。进行以下查询:

UPDATE users
SET first_name = 'Michael'
WHERE users.id = 123

即使将查询更新为相同的值,查询也会实际执行该更新吗?如果是这样,我如何防止它发生?


1
为什么要执行一条语句,同时又期望它不执行?
Max Vernon

@MaxVernon Ruby on Rails的ORM不会更新记录,因此我很好奇PostgreSQL是否做了同样的事情。
OneSneakyMofo 2015年

1
我建议Ruby on Rails是否这样做,它可能首先进行选择,以查看该行是否需要更新。
Max Vernon

Answers:


35

由于Postgres 的MVCC模型,并且根据SQL的规则,UPDATEa会为子句中未排除的每一行写一个新的行版本WHERE

确实对性能产生了或多或少的直接或间接影响。“空更新”每行的成本与任何其他更新相同。它们像其他任何更新一样触发触发器(如果存在),必须对其进行WAL记录,并且它们会产生死行,使表膨胀,并VACUUM像其他任何更新一样导致以后进行更多工作。

索引条目和更改任何涉及的列的TOASTed可以保持不变,但是对于任何更新的行都是如此。有关:

排除这样的空更新几乎总是一个好主意(如果确实有可能发生)。您没有在问题中提供表定义(这总是一个好主意)。我们必须假设first_name可以为NULL(对于“名字”来说并不奇怪),因此查询必须使用NULL安全比较

UPDATE users
SET    first_name = 'Michael'
WHERE  id = 123
AND   first_name IS DISTINCT FROM 'Michael';

如果first_name IS NULL在更新之前,带有just的测试first_name <> 'Michael'将评估为NULL,因此从更新中排除该行。鬼error的错误。如果定义NOT NULL了column ,那么请使用简单的相等性检查,因为这样做便宜一些。

有关:


1
Indexes entries and TOASTed columns where none of the involved columns are changed can stay the same但是,是否不必更新它们以指向该行的新位置?
dvtan

1
@dtgq:不适用于HOT更新,索引可以继续指向旧位置,并且堆提取必须遍历HOT链才能获取活动元组。我在上面添加了指向更多解释的链接。
Erwin Brandstetter

1
MVCC要求进行noop更新以编写新的元组怎么办?
jberryman

@jberryman:不确定我是否理解。无论哪种方式,都请以新问题提问。您可以始终链接到该上下文。您可以在这里发表评论以链接回去(并引起我的注意)。
Erwin Brandstetter

2
@jberryman:我实际上不知道该项目以这种方式进行的原因。那是很久以前建立的。但是我认为检查每一行是否相等并为未更改的行使用单独的代码路径会不必要地昂贵。事务ID的处理将更为复杂-特殊的大小写rollback,快照处理,锁管理,WAL,等等……
Erwin Brandstetter

4

像Ruby on Rail一样,ORM提供了延迟执行,该执行将一条记录标记为已更改(或未更改),然后在需要或调用时将其提交到数据库。

PostgreSQL是数据库而不是ORM。如果花时间检查新值是否与查询中的更新值相同,则会降低性能。

因此,它将更新该值,而不管它是否与新值相同。

如果您想避免这种情况,可以使用Max Vernon在其答案中建议的代码。


2

您可以简单地添加以下where子句:

UPDATE users
SET first_name = 'Michael'
WHERE users.id = 123
    AND (first_name <> 'Michael' OR first_name IS NULL);

如果first_name定义为NOT NULL,则OR first_name IS NULL可以删除零件。

条件:

(first_name <> 'Michael' OR first_name IS NULL)

也可以写得更优雅一些(在欧文的答案中):

first_name IS DISTINCT FROM 'Michael'

不知道该列是否可以为NULL,这可能会引起一个偷偷摸摸的错误。
Erwin Brandstetter 2015年

1
@ErwinBrandstetter我正在更新答案-然后我看到了评论和您的答案!
ypercubeᵀᴹ

感谢您的编辑@ypercube-以及有关NULL@erwin 的评论
Max Vernon

1

从数据库的角度

您问题的答案是“是”。更新将进行。数据库不检查以前的值,它仅设置新值。

由于这发生在内存中(并且只会在发出提交后才写入数据文件),因此性能不会成为问题。

从ORM角度

通常,您将有一个对象代表数据库的一行(它可能比这要复杂得多,但让我们保持简单)。该对象在内存中(在应用服务器级别)进行管理,并且只有该对象的最新提交版本才会在某个特定点将其实际存储到数据库中。

这可以解释不同的行为。

现在,我们不要将货船与3D打印机进行比较。您可以使用货船发送3D打印机这一事实并不意味着它们之间可以进行任何比较。

请享用!

我希望这可以澄清一些概念。


4
性能是关键。每个更新都必须写入磁盘(日志和表)。
ypercubeᵀᴹ

这将取决于您使用的实际RDBMS。但是它们大多数不会提交每个更新,而只会提交它们在内存中的最后一个提交块。您永远不会在数据库中读取或写入一行。您读取/写入块并将其保留在内存中,直到必须将其清除以将新块放在同一位置为止。在内存中时,并非行中的所有更改都将写入磁盘,而仅在发出“数据库编写器”过程的信号时将块内容写入该数据块中。因此,不...除非您的应用程序长时间保持未提交的块,否则这不是问题。
Silvarion

1
问题是关于Postgres,而不是任何DBMS。尽管更新不必全部一一写入,但数据库上的每次写入都必须写入日志。如果未将更改写入持久性存储,那么DBMS将如何在系统崩溃后幸免?
ypercubeᵀᴹ

是的,它在检查点期间也从内存写入日志。除非您有大量的并发用户,否则这根本不是问题。日志也成批写入。我认为我们正在谈论服务器。如果您在谈论带有5400RPM HDD的笔记本电脑中的Postgres数据库,是的……您将始终遇到性能问题。因此,最终答案将是第一个答案。它取决于太多的事情。
Silvarion
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.