【问题标题】:Redundant data in update statements更新语句中的冗余数据
【发布时间】:2011-12-10 00:01:44
【问题描述】:

Hibernate 生成 UPDATE 语句,包括所有列,无论我是否更改了这些列中的值,例如:

tx.begin();
Item i = em.find(Item.class, 12345);
i.setA("a-value");
tx.commit();

发出此UPDATE 声明:

update Item set A = $1, B = $2, C = $3, D = $4 where id = $5

所以BCD 列已更新,而我没有更改它们。

说,项目经常更新,所有列都有索引。 把 Hibernate 部分优化成这样有意义吗?

tx.begin();
em.createQuery("update Item i set i.a = :a where i.id = :id")
    .setParameter("a", "a-value")
    .setParameter("id", 12345)
    .executeUpdate();
tx.commit();

最让我困惑的是,'未优化'和'优化'查询版本的EXPLAIN计划是一样的!

【问题讨论】:

  • 显然不是所有的 JPA 实现都会发布这样的声明;例如,DataNucleus 知道哪些字段被修改,因此只包含 UPDATE 语句中的字段

标签: sql postgresql hibernate jpa sql-execution-plan


【解决方案1】:

由于PostgreSQL MVCCUPDATE 实际上很像DELETE 加上INSERT。除了烤值的显着例外 - 请参阅:

(仅堆元组的细微差别 - DELETE + INSERT 启动一个新的 HOT 链 - 但这与手头的情况无关。)

确切地说,“已删除”行对于在提交删除后开始的任何事务都是不可见的,并且稍后会被清理。因此,在数据库方面,包括索引操作在内,这两条语句实际上没有区别。 (有例外情况,请继续阅读。)它会稍微增加网络流量(取决于您的数据)并且需要进行一些解析。

在@araqnid 的输入之后,我进一步研究了 HOT 更新并进行了一些测试。就 HOT 更新而言,实际上不会更改值的列的更新没有任何区别。我的回答成立。请参阅下面的详细信息。

这也适用于 toasted 属性,因为除非值实际更改,否则这些属性也不会被触及。

但是,如果您使用每列触发器(在 pg 9.0 中引入),这可能会产生不希望的副作用!

我引用the manual on triggers:

...诸如UPDATE ... SET x = x ... 之类的命令将触发触发器 列x即使列的值没有改变

我的大胆强调。

抽象层是为了方便。它们对于不了解 SQL 的开发人员或者如果应用程序需要在不同的 RDBMS 之间移植时很有用。不利的一面是,它们会影响性能并引入额外的故障点。我尽可能避免使用它们。

HOT(仅堆元组)更新

仅堆元组是随 Postgres 8.3 引入的,对 8.3.48.4.9 进行了重要改进。
The release notes for Postgres 8.3:

UPDATEs 和 DELETEs 留下死元组,失败的 INSERTs 也是如此。 以前只有VACUUM 可以回收死元组占用的空间。和 HOT死元组空间可以在执行时自动回收 INSERTUPDATE 如果没有对索引列进行更改。这 允许更一致的性能。此外,HOT 避免添加 重复的索引条目。

强调我的。并且“无更改”包括使用与它们已有的相同值更新列的情况。我实际测试过,我不确定。

最终,广泛的README.HOT in the source code 证实了这一点。

Toasted 列也不妨碍 HOT 更新。 HOT 更新的元组只是链接到关系的 toast 分支中相同的、未更改的元组。热更新甚至适用于目标列表中的烤值(实际更改与否)。如果 toast 的值发生变化,显然需要写入 toast 关系分支。我也测试了所有这些。

不要相信我的话,你自己看看。 Postgres 提供了几个functions to check statistics。运行您的 UPDATE 有和没有所有列,并检查它是否有任何区别。

-- Number of rows HOT-updated in table:
SELECT pg_stat_get_tuples_hot_updated('table_name'::regclass::oid)

-- Number of rows HOT-updated in table, in the current transaction:
SELECT pg_stat_get_xact_tuples_hot_updated('table_name'::regclass::oid)

或使用pgAdmin。选择您的表格并检查主窗口中的“统计”选项卡。

请注意,只有在主关系分支的同一页面上有新元组版本的空间时,才可能进行 HOT 更新。强制该条件的一种简单方法是使用仅包含几行的小表进行测试。页面大小通常为 8k,因此页面上必须有可用空间。

【讨论】:

  • 8.4 左右引入的仅堆元组功能实际上确实优化了 UPDATE 与 DELETE+INSERT 相比,因为它保持索引条目指向行的旧版本而不是创建新版本。大概这取决于 UPDATE 语句中的列列表来了解 UPDATE 是否可以创建仅堆元组。如果是这样,总是发送所有列的值将意味着永远不会为具有除主键之外的任何索引的表创建仅堆元组。
  • 所以你的意思是更新 A 将花费我更新 index(A)、index(B)、index(C) 和 index(D)?
  • @araqnid:我花了一个多小时进行调查和测试,因为我的评论非常有趣 (+1)。事实证明,在这种情况下,HOT 更新不会受到影响。行为取决于列实际更改,而不是UPDATE 语句中的列列表。有关详细信息,请参阅我修改后的答案。
  • 啊是的,就是这样。奇怪的是,它以一种方式用于 HOT,而以不同的方式用于触发器。您也可以使用 pageinspect 扩展来查看效果,这里有一个演示脚本:gist.github.com/1298358
  • @ErwinBrandstetter 我正在阅读您的答案并有一个问题:如果我们更新非索引大文本列(使用 TOAST),更新是否热?
【解决方案2】:

可以使用hibernate注解@Entity:

@org.hibernate.annotations.Entity(dynamicUpdate = true)
public class Item

这将仅更新已更改的字段。

【讨论】:

  • 这因情况而异。 Hibernate 文档 (tinyurl.com/6e6r7yz) 建议您检查特定情况下的性能影响:dynamic-update 和 dynamic-insert 设置...在某些情况下可以提高性能,但实际上会降低其他情况下的性能。我>
  • Google 表示 dynamicUpdate 将花费额外的 CPU 来为语句生成 SQL,因为语句不再被缓存:(
  • 我认为数据库端和 Hibernate 端的开销之间存在权衡。但与往常一样,您应该针对您的应用对其进行测量。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-26
  • 2018-12-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多