如何在最终获得“40001 由于并发更新而无法序列化访问”之前避免长时间延迟

How to avoid long delay before finally getting "40001 could not serialize access due to concurrent update"

我们有一个 Postgres 12 系统 运行 一个 master master 和两个异步热备份副本服务器,我们使用 SERIALIZABLE 事务。所有数据库服务器都有非常快速的 Postgres SSD 存储和 64 GB RAM。如果客户端不能接受事务的延迟数据,则它们会直接连接到主服务器。接受最多 5 秒旧数据的只读客户端使用副本服务器查询数据。只读客户端使用可重复读取事务。

我知道因为我们使用 SERIALIZABLE 事务,Postgres 可能会给我们误报匹配并迫使我们重复事务。这很好,符合预期。

但是,我看到的问题是 随机地,单行 INSERT 或 UPDATE 查询停滞了很长时间。例如,一种错误情况如下(直接与 master 对话以允许修改 table 数据):

一个简单的单行插入

insert into restservices (id, parent_id, ...) values ('...', '...', ...);

在最终发出错误之前停顿了 74.62 秒

ERROR 40001 could not serialize access due to concurrent update

有错误上下文

SQL statement "SELECT 1 FROM ONLY "public"."restservices" x WHERE "id" OPERATOR(pg_catalog.=)  FOR KEY SHARE OF x"

我们记录所有超过 40 毫秒的查询,所以我知道这种停顿很少见。就像一天可能有几个查询。在正常负载期间,我们平均每秒处理 200-400 个事务,每个事务有 5-40 个查询。

最终出现上述错误后,客户端代码自动释放了两个保存点,回滚了事务并断开了与数据库的连接(此清理总共花费了 2 毫秒)。然后它在 2 毫秒后重新连接到数据库,并从头开始重放整个事务,并在 66 毫秒内完成,包括连接到数据库的时间。所以我认为这与客户端或主服务器整体的性能无关。预计交易时间在 5-90 毫秒之间,具体取决于交易。

是否有一些 PostgreSQL 连接或主配置设置可以使 PostgreSQL return 错误 40001 更快,即使它导致更多事务被回滚? 有谁知道如果设置

set local statement_timeout='250'

事务内有危险的副作用?根据文档 https://www.postgresql.org/docs/12/runtime-config-client.html“不建议在 postgresql.conf 中设置 statement_timeout,因为它会影响所有会话”,但我可以仅为该客户端的事务设置超时,该客户端能够自动重试交易速度非常快。

还有什么可以尝试的吗?

看起来有人锁定了您尝试插入的行的父行。在锁被释放之前,PostgreSQL 不知道该怎么做,所以它会阻塞。如果您失败了而不是阻塞,并且在失败时重试了完全相同的事情,则相同的父行(很可能)仍会被锁定,因此只会再次失败,您将忙于等待。忙等待不好,所以在这里阻塞而不是失败通常是一件好事。它阻塞然后解除阻塞只会失败,但一旦失败重试应该会成功。

阻止优于失败的一个明显例外是,当您重试时,您可以选择不同的父行来重试,如果这在您的上下文中有意义的话。在这种情况下,也许最好的办法是在尝试插入之前使用 NOWAIT 显式锁定父行。这样你也许可以以更微妙的方式处理失败。

如果您必须使用相同的 parent_id 重试,那么我认为唯一真正的解决方案是找出谁持有父行锁这么久,并解决这个问题。我不认为设置 statement_timeout 会有危险,但它也不会解决您的问题,因为您可能会继续重试,直到释放违规行的锁。 (在持有锁的另一个会话上设置它可能会有所帮助,具体取决于 那个 会话在持有锁时正在做什么。)