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

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


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

评论

x发布到SO:stackoverflow.com/q/33156712/939860

#1 楼

由于Postgres的MVCC模型,并且根据SQL规则,UPDATE会为WHERE子句中未排除的每一行编写一个新的行版本。
直接或间接地对绩效产生重大影响。 “空更新”每行的成本与任何其他更新相同。它们像其他任何更新一样触发触发器(如果存在),它们必须进行WAL记录,并且会产生死行,使表膨胀,并像其他任何更新一样为VACUUM带来更多工作。所涉及的列均未更改的TOASTed列可以保持不变,但是对于任何更新的行都是如此。相关:


PostgreSQL初始数据库大小
更新语句中的冗余数据

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

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


如果在更新前进行first_name IS NULL,则仅进行first_name <> 'Michael'的测试将评估为NULL,因此从更新中排除该行。鬼error的错误。如果该列定义为NOT NULL,则使用简单的相等性检查,因为这样会便宜一些。 )在多个列上选择DISTINCT?
用来自另一个表的数据更新列


评论


我要求您弄清(论证)答案第一句中的主张(这就是答案的评论部分适用于afaiu)。我完全不清楚为什么MVCC会在postgres上强制这种操作行为。我怀疑触发器,一些细节。复制或其他一些更无聊的问题实际上是使pg在没有更新的情况下避免生成无效元组的原因。

– jberryman
18年5月24日在10:22



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

–欧文·布兰德斯特(Erwin Brandstetter)
18年5月24日在11:29

#2 楼

像Ruby on Rail一样,ORM提供了延迟执行,该执行将记录标记为已更改(或未更改),然后在需要或调用时将更改提交到数据库。如果花时间检查新值是否与查询中的更新值相同,则会降低性能。

因此它将更新该值,而不管其值是否与是否提供新值。

如果要防止这种情况发生,可以使用Max Vernon在其答案中建议的代码。

#3 楼

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

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


也可以更优雅地写为(根据欧文的回答):

#4 楼

从数据库的角度来看

您的问题的答案是肯定的。更新将进行。数据库不会检查先前的值,它只会设置新值。

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

从ORM角度来看

通常,您将有一个对象代表数据库的一行(它可能比这要复杂得多,但让我们保留它简单)。该对象在内存中(在应用服务器级别)进行管理,并且只有该对象的最新提交版本才会在某个特定点将其实际提交到数据库。 br />
现在,我们不要将货船与3D打印机进行比较。您可以使用货船发送3D打印机这一事实并不意味着它们之间可以进行任何比较。

享受吧!

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

评论


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

–超立方体ᵀᴹ
15-10-16在7:01



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

–银杏
15年10月16日在12:30

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

–超立方体ᵀᴹ
2015年10月16日在13:19

是的,它在检查点期间也从内存写入日志。除非您有大量的并发用户,否则这完全不是问题。日志也成批写入。我认为我们正在谈论服务器。如果您在谈论带有5400RPM HDD的笔记本电脑中的Postgres数据库,是的……您将始终遇到性能问题。因此,最终答案将是第一个答案。它取决于太多的事情。

–银杏
15-10-16在13:48