PostgreSQL 反模式:与成群结队的“死人”作斗争

PostgreSQL 内部机制的特殊性使其在某些情况下非常快,而在另一些情况下“不太快”。 今天我们将重点讨论 DBMS 的工作方式与开发人员使用它的用途之间的冲突的经典示例 - UPDATE 与 MVCC 原则.

简短的故事来自 отличной статьи:

当通过 UPDATE 命令修改一行时,实际上执行了两个操作:DELETE 和 INSERT。 在 字符串的当前版本 xmax 设置为等于执行 UPDATE 的事务数。 然后就创建完成了 新版本 同一条线; 其xmin值与之前版本的xmax值一致。

此事务完成后的某个时间,旧版本或新版本,取决于 COMMIT/ROOLBACK,会被认可 “死”(死元组) 经过时 VACUUM 根据表并清除。

PostgreSQL 反模式:与成群结队的“死人”作斗争

但这不会立即发生,但“死者”的问题可以很快出现 - 通过重复或 大量更新记录 在一张大桌子上,过一会儿你也会遇到同样的情况 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;

现在数据库中有该记录的多少个版本? 是的,4块! 其中,3 个是相关的,XNUMX 个必须在您之后通过 [auto]VACUUM 进行清理。

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

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

#2:使用与卢克不同!

所以,你还想要 更新表中很多很多记录 (в ходе применения скрипта или конвертера, например). И в скрипт летит что-то такое:

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 和其他逻辑运算符来帮助:
PostgreSQL 反模式:与成群结队的“死人”作斗争
...以及一些关于复杂操作的信息 ROW()- 表达式:
PostgreSQL 反模式:与成群结队的“死人”作斗争

#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号决定: 愚蠢的电话

但这正是你应该发生的事情 使用相同的记录同时工作? 或者,例如,您是否搞乱了在客户端调用业务逻辑的算法? 如果你想一想?

来源: habr.com

添加评论