PostgreSQL Antipatterns:与“死神”战斗

内部PostgreSQL机制的功能允许它在某些情况下非常快,而在其他情况下则不那么快。今天,我们将讨论一个经典的示例,它说明了DBMS如何工作与开发人员如何使用它发生冲突-UPDATE与MVCC原理

简要介绍一篇出色的文章
当用UPDATE命令修改一行时,实际上执行两个操作:DELETE和INSERT。当前版本的行中,将xmax设置为等于执行UPDATE的事务数。然后创建同一行新版本其xmin值与先前版本的xmax值匹配。
完成此事务后的一段时间,旧的或新的版本(取决于哪个版本通过表并进行清理COMMIT/ROOLBACK将被识别为“死”(死元组)VACUUM



但这不会立即发生,但是可以很快解决“死角”的问题- 在大型表中多次或大规模更新记录,不久之后,VACUUM将无济于事

#1:我喜欢移动它


假设您的业务逻辑方法本身有效,并且突然意识到有必要在某些记录中更新字段X:

UPDATE tbl SET X = <newX> WHERE pk = $1;

然后,随着进度的进行,发现Y字段也应更新:

UPDATE tbl SET Y = <newY> WHERE pk = $1;

...然后还有Z-为什么要花些时间?

UPDATE tbl SET Z = <newZ> WHERE pk = $1;

现在该记录在数据库中有多少版本?是的,四块!其中一个是相关的,另外三个将为您选择[自动] VACUUM。

不要这样!在一个请求中使用所有字段更新 -几乎总是可以像这样更改方法的逻辑:

UPDATE tbl SET X = <newX>, Y = <newY>, Z = <newZ> WHERE pk = $1;

#2:使用IS DISTINCT FROM,卢克!


因此,您仍然想更新表中的许多记录(例如,在使用脚本或转换器的过程中)。像这样的东西会飞入脚本中:

UPDATE tbl SET X = <newX> WHERE pk BETWEEN $1 AND $2;

大约以这种形式,经常会遇到查询,几乎总是不填写空白的新字段,而是要纠正数据中的某些错误。此外,根本不考虑已经存在的数据正确性,但是徒劳!就是说,记录正被重写,即使那正是我想要的-为什么?正确:

UPDATE tbl SET X = <newX> WHERE pk BETWEEN $1 AND $2 AND X IS DISTINCT FROM <newX>;

许多人不知道这样一个出色的运算符的存在,因此以下是IS DISTINCT FROM其他逻辑运算符可以帮助您的备忘单

...以及有关复杂ROW()表达式的操作的一些知识

#3:我会通过...阻止来认识我的亲爱的


运行两个相同的并行过程,每个过程都针对记录标记,该过程处于“正在运行”状态:

UPDATE tbl SET processing = TRUE WHERE pk = $1;

即使这些进程实质上是彼此独立地执行操作,但在一个ID的框架内,在此请求下,第二个客户端也会“锁定”直到第一个事务完成。

解决方案1:将任务简化为上一个任务,

只需再次添加IS DISTINCT FROM

UPDATE tbl SET processing = TRUE WHERE pk = $1 AND processing IS DISTINCT FROM TRUE;

以这种形式,第二个请求将不会更改数据库中的任何内容,因为它已经“应有尽有”-因此,不会发生阻塞。此外,记录的“不存在”的事实已经在所应用的算法中进行了处理。

决策2:咨询锁

一个单独的文章的大主题,您可以在其中阅读有关应用程序方法和推荐锁的“介绍”

解决方案3:没有[d]智能调用

但是,确切地说,您应该同时处理同一记录吗?还是例如,您还是搞砸了客户端业务逻辑调用算法?如果您考虑过?

All Articles