提问者:小点点

如何避免长时间延迟才最终拿到“40001因并发更新无法序列化访问”


我们有一个Postgres 12系统,运行一个主服务器和两个异步热备副本服务器,我们使用SERIALIZABLE事务。所有数据库服务器都有非常快的PostgresSSD存储和64 GB的RAM。如果客户端不能接受事务的延迟数据,则直接连接到主服务器。接受5秒前数据的只读客户端使用副本服务器查询数据。只读客户端使用REPEATABLE READ事务。

我知道,因为我们使用SERIALIZABLE交易,Postgres可能会给我们误报匹配并迫使我们重复交易。这很好,也是意料之中的。

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

一个简单的单行插入

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.=) $1 FOR KEY SHARE OF x"

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

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

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

set local statement_timeout='250'

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

还有什么可以尝试的吗?


共1个答案

匿名用户

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

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

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