为什么Postgres交易冻结这么慢?以及如何解决?

时间:2020-02-11 10:38:38

标签: postgresql performance

希望有更深的Postgres /数据库知识的人可以分享一些见识。

背景:

我有一个Postgres v11数据库,其中运行着一些数据。现在,我想作为一个事务在其上运行约20万次操作(它们应该全部成功,或者不进行任何更改)。这些事务包括INSERT和UPDATE语句的混合,还涉及数据库中的一些触发器。

如果我在事务中运行查询,则大约需要70分钟

BEGIN;
// statement
// statement
// statement
// ...
COMMIT;

如果我删除周围的事务块并仅运行查询,则将花费7分钟。

// statement
// statement
// statement
// ...

现在,我怀疑性能差异与事务保留一些临时表有关,这些临时表会遇到缓冲区大小或索引问题,从而导致速度变慢,但是由于我对Postgres内部没有深入的了解,因此我实际上并不了解。我已经尝试过更改各种WAL设置,但是没有明显的不同。

所以我有2个问题:

  1. 为什么交易这么慢?
  2. 如何在不降低性能的情况下运行可能回滚的批量操作?

编辑1


有关表和查询的其他信息

在不透露具体细节的情况下,我将尽我所能提供尽可能多的信息。

有5个与此交易相关的主表-accountscurrenciestransactionstransaction_logsbalance

transactions表具有3个外键-2至accounts和1至currencies

balances表具有2个外键-1到accounts和1到currencies

transaction_logs表具有3个外键-1到transactions,1到accounts,1到currencies

批处理开始于插入所有相关的帐户和货币。 (约1万个帐户和2种货币)

接下来,它是插入和事务表更新的混合-插入在一个插入中分组最多300行,而更新基于PK,因此一次仅更新1行。总共大约有14万次插入和6万次更新。

插入和更新操作都依次执行触发器,该触发器将更新余额表并将两行插入transaction_log表中。

典型的插入看起来像

INSERT INTO transactions (from_account_id, to_account_id, currency_id, amount, metadata, status) VALUES (1, 1, 1, 10.00, '{"json":"blob"}', 'pending');

典型更新如下

UPDATE transactions SET status = 'finished' WHERE id = 1;

编程环境为node.js,驱动程序为pg


编辑2


对每个人都无法提供更多信息深表歉意-我试图创建一个封闭的测试环境以重现该错误。在这样做的同时,我设法解决了问题的症结。结构本身似乎无关紧要。 变慢是由于在同一笔交易中一遍又一遍地更新同一行引起的。

我管理的最简单的复制是创建一个包含2个字段的表,添加几行,然后重复更新第一行。没有交易,操作时间保持相对恒定。但是,在一笔交易中,交易开始攀升-大约10万次更新,比开始时高出约3倍。在我的计算机上,进行10万次更新测试的总运行时差异是交易版本和非交易版本之间的2.5倍。

但是,如果您的数据更加多样化-而不是更新同一行,而是更新不同的行,那么就不会出现此问题,并且使用分布良好的数据,交易实际上会更快。

PS。在v12上也进行了测试,效果大致相同。

有人知道为什么会这样吗?


编辑3


我已经创建了一个存储库来演示此问题https://github.com/DeadAlready/pg-test

2 个答案:

答案 0 :(得分:2)

我把这个问题带到了postgres官方邮件列表中,这就是我得到的答案,我将在这里分享给以后的搜索者。


TL; DR;

  • 在事务中多次更新单行很慢
  • 这是预期的
  • 避免这样做

链接到线程:

https://www.postgresql.org/message-id/flat/7624.1581628574%40sss.pgh.pa.us

完整答案:

是的,这并不奇怪。每个新更新都会创建一个新版本的 它的行。当您在单独的交易中进行交易时, 事务N + 1提交,系统可以识别该行版本 由交易N创建的交易已失效(不再对任何人可见),并且 回收它,允许磁盘上存在的行版本数 保持大致恒定。但是,没有那么好 通过事务仍然创建的行版本的内部管理 运行。因此,当您在一笔交易中进行N次更新时, 成为磁盘上N个注定但尚未回收的行版本。

除了磁盘空间膨胀之外,这还很糟糕,因为以后的更新 必须浏览由早期更新创建的所有行版本, 寻找他们应该更新的版本。所以你有一个O(N ^ 2) 与此相关的成本,这无疑是您所观察的。

除了“不要那样做”之外,没有其他任何真正好的解决方案。 David在附近使用临时表的建议无济于事,因为 无论表是临时表还是常规表,这种行为都是相同的。

原则上,也许我们可以改善死行的粒度 检测,这样,如果行版本既被创建又被删除 当前的交易,我们没有可以 看到它,我们可以继续并将行标记为已死。但是还不清楚 那将是值得的额外费用。当然没有现有的PG 发布会尝试这样做。

致谢,汤姆·莱恩

答案 1 :(得分:0)

  1. 为什么交易这么慢?
    • 由于日志写入的开销,小型事务非常昂贵。哪个重复了外部操作的成本。
    • 由于管理费用的增加,大笔交易非常昂贵。

根据我的经验,有一个最佳的中等交易规模。

  1. 如何在不降低性能的情况下运行可能回滚的批量操作?

此事务中有许多新记录。此交易仅更改那些新记录吗?该算法对以前已经存在的记录的影响有多大。我的想法是,您可以非常快速地在多个中间范围内构建数据,并且在必要时通过删除这些新记录在单独的事务中删除这些新数据。

如果现有记录已更改,则可以在算法末尾的一个较小事务中移动对这些现有记录的处理。如果需要回滚,则可以回滚此小事务,然后删除希望可以很容易识别的那些新记录。