什么会导致 "idle in transaction" for "BEGIN" 语句

What can cause "idle in transaction" for "BEGIN" statements

我们有一个 node.js 应用程序,它通过 pg-promise 连接到 Postgres 11 服务器 - 所有进程都 运行 在 docker 容器中的单个云服务器上。
有时我们会遇到应用程序不再响应的情况。

上次发生这种情况时,我有一点时间通过 pgadmin 检查数据库,它显示连接是 idle in transaction 语句 BEGIN 和独占锁 virtualxid

我觉得情况是这样的:

  1. 应用程序通过向 db
  2. 发送 BEGIN sql 命令开始了一个事务
  3. db得到这个命令,开始了一个新的事务,从而获得了virtualxid
  4. 模式的独占锁
  5. 现在 db 等待应用程序发送下一个 statement/s(直到它收到 COMMITROLLBACK)- 然后它将释放模式 [= 的独占锁14=]
  6. 但由于某种原因它不再获得语句:
    我认为 node.js 事件循环被阻塞了——因为当时,当我们看到这些锁时,node.js 应用程序不再记录语句。但是网络服务器仍然收到请求并报告了一些 upstream timed out 请求。

这有意义吗(我真的不确定 2. 和 3.)?
为什么所有交易一开始都会阻塞?这只是巧合还是显示的 SQL 可能有误?

顺便说一句:在此 I found, that we can set idle_in_transaction_session_timeout 中,以便这些事务将在超时后释放 - 这很好,但我试图了解导致此问题的原因。

事务根本没有阻塞。数据库正在等待应用程序发送下一条语句。

事务 ID 上的锁只是事务相互阻塞的一种技术,即使它们不争用 table 锁(例如,如果它们正在等待行锁):每个事务都在自己的事务 ID 上持有独占锁,如果它必须等待并发事务完成,它可以只请求锁定该事务的 ID(并被阻止)。

如果所有交易看起来都是这样,那么锁一定在您的应用程序中;不涉及数据库。

在查找数据库中阻塞的进程时,在 pg_locks 中查找 granted 为假的行。

你的解释是正确的。至于为什么会这样,那就不好说了。在您的应用程序中似乎存在某种错误(可能是未检测到的死锁),或者可能在 nodes.js 或 pg-promise 中。您将必须在该级别进行调试。

不出所料,问题是由我们的应用程序代码引起的。交易使用不当:

  • 其中一个 REST 端点使用 Database.tx() 立即开始新事务。
  • 这笔交易被向下传递了多个级别,但是链中的一个函数出错并传递了undefined而不是交易到下一级
  • 最低存储库级别函数启动了一个新事务(因为事务参数是undefined),第二次使用Database.tx()

这在重负载下开始失败:

  • 连接池大小设置为 10
  • 当有很多同时请求此端点时,我们遇到了 10 个请求开始(打开外部事务)并且尚未到达将请求第二个事务的存储库代码的情况。
  • 当这些请求到达存储库代码时,它们会从连接池中请求一个新的(第二个)连接。但是这个调用会阻塞,因为当前所有连接都在使用中。
  • 所以我们有一个讨厌的应用程序级死锁

所以解决方案是修复应用程序代码(中间函数必须正确传递事务)。然后一切正常。

此外,我强烈建议设置一个合理的idle_in_transaction_session_timeoutconnection-timeout.那么,即使以后的版本再次引入这样的应用程序死锁,应用程序也可以在这个超时后自动恢复。
备注:

  • v 10.3.4 之前的 pg-postgres 包含与 connection-timeout
  • 相关的 small bug #682
  • 版本 10.3.5 之前的 pg-promise 无法从 idle-in-transaction-timeout 恢复并使连接处于断开状态:参见 pg-promise #680

基本上还有另一个问题:不需要使用事务——因为所有函数都只是读取数据:所以我们可以只使用 Database.task()而不是 Database.tx()