更新语句中的冗余数据

Tai*_*air 7 postgresql hibernate jpa sql-execution-plan

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

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

发表此UPDATE声明:

update Item set A = $1, B = $2, C = $3, D = $4 where id = $5
Run Code Online (Sandbox Code Playgroud)

因此列B,C,D会更新,而我没有更改它们.

比如,项目经常更新,所有列都被编入索引.问题是:将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();
Run Code Online (Sandbox Code Playgroud)

最让我困惑的是,EXPLAIN"未经优化"和"优化"查询版本的计划完全相同!

Erw*_*ter 11

由于PostgreSQL MVCC,UPDATE实际上几乎就像一个DELETE加号INSERT- 除了值得注意的值之外.看到:

确切地说,"删除"行对于在提交删除之后开始的任何事务是不可见的,并且稍后被清除.因此,在数据库方面,包括索引操作,实际上两个语句之间没有区别.(例外适用,继续阅读.)它会稍微增加网络流量(取决于您的数据)并需要一些解析.

在@ araqnid的输入之后我研究了HOT更新并进行了一些测试.就HOT更新而言,对实际没有更改值的列的更新没有任何区别.我的回答成立.详情见下文.

这也适用于烘烤属性,因为除非值实际改变,否则也不会触及这些属性.

但是,如果您使用每列触发器(与第9.0版一起引入),则可能会产生不良副作用!

我引用触发器手册:

...一个命令UPDATE ... SET x = x ...会触发列上的触发器x,即使列的值没有改变.

大胆强调我的.

抽象层是为了方便起见.它们对于SQL文盲的开发人员非常有用,或者如果应用程序需要在不同的RDBMS之间移植.在不利方面,他们可以屠宰性能并引入额外的失败点.我尽可能地避开它们.

关于HOT(仅限堆元组)更新

Postgres 8.3引入了仅堆元组,在8.3.48.4.9中有重要改进.
Postgres 8.3的发行说明:

UPDATEs和DELETEs留下了死去的元组,失败了INSERT.以前只能VACUUM收回死元组占用的空间.使用HOT死元组空间可以在索引列INSERT或者UPDATE 如果没有对索引列进行更改时自动回收 .这样可以实现更一致的性能.此外,HOT避免添加重复的索引条目.

强调我的.并且"无更改"包括使用与已存在的值相同的值更新列的情况.我实际上刚刚测试过,因为我不确定.

烤柱也不妨碍HOT更新.HOT更新的元组只链接到关系的toast fork中相同的,未更改的元组.HOT更新甚至可以使用目标列表中的烘焙值(实际更改与否).如果改变了烘烤的值,显然需要写入toast关系分支.我也测试了所有这些.

你不必接受我的话.看看自己,Postgres提供了几个检查统计数据功能.运行您的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)
Run Code Online (Sandbox Code Playgroud)

或者使用pgAdmin.选择表并检查主窗口中的"统计"选项卡.

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

  • 与DELETE + INSERT相比,8.4左右引入的仅用于堆的元组功能实际上确实优化了UPDATE,因为它使索引条目保持指向行的旧版本而不是创建新版本.据推测,这取决于UPDATE语句中的列列表,以了解UPDATE是否可以创建仅限堆的元组.如果是这样,那么始终发送所有列的值意味着永远不会为具有除主键之外的任何索引的表创建仅堆的元组. (3认同)
  • 是的,就是这样.奇怪的是,它为HOT工作的一种方式和触发器的不同方式.您也可以使用pageinspect扩展来查看效果,这是一个演示该脚本的脚本:https://gist.github.com/1298358 (2认同)