【问题标题】:What can cause "idle in transaction" for "BEGIN" statements什么可能导致“BEGIN”语句的“事务空闲”
【发布时间】:2020-03-28 10:26:19
【问题描述】:

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

上次发生这种情况时,我有一点时间通过 pgadmin 检查数据库,它显示连接是idle in transaction,声明为BEGIN,排他锁为virtualxid

我认为情况是这样的:

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

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

顺便说一句:在answer 中我发现,我们可以设置idle_in_transaction_session_timeout 以便在超时后释放这些事务 - 这很好,但我试图了解导致此问题的原因。

【问题讨论】:

  • 您没有在应用程序的某处执行松散的BEGIN,是吗?如果它都是 pg-promise 库的方法 tx 的一部分,那么应该遵循以下查询。也许您的事务逻辑由于某些原因未能执行与事务相关的查询?

标签: node.js postgresql pg-promise


【解决方案1】:

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

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

如果所有事务都像这样,那么锁一定在你的应用程序的某个地方;不涉及数据库。

当查找数据库中阻塞的进程时,查找pg_locksgranted 为假的行。

【讨论】:

    【解决方案2】:

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

    【讨论】:

    • 库中不能有任何死锁,否则早在几年前就被发现了。它必须在应用程序中。
    • @vitaly-t 我也不认为这是由 pg-promise 引起的。我想这可能与基础设施有关:例如阻塞的 TCP 套接字,或文件系统阻塞(例如,当某些部分尝试写入日志文件或 stdout/err 时)。但也许你有一些想法/提示,为什么所有调用都可能在 BEGIN 语句之后阻塞:我在应用程序中找不到任何 BEGIN,所以这是由 pg-promise 完成的 - 但之后它应该立即发送应用程序查询,对吧?
    【解决方案3】:

    正如预期的那样,问题是由我们的应用程序代码引起的。交易使用不正确:

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

    这开始失败,在重负载下:

    • 连接池大小设置为 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()

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-30
    • 2011-08-26
    • 1970-01-01
    相关资源
    最近更新 更多