什么会导致 "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
我觉得情况是这样的:
- 应用程序通过向 db
发送 BEGIN
sql 命令开始了一个事务
- db得到这个命令,开始了一个新的事务,从而获得了
virtualxid
模式的独占锁
- 现在 db 等待应用程序发送下一个 statement/s(直到它收到
COMMIT
或 ROLLBACK
)- 然后它将释放模式 [= 的独占锁14=]
- 但由于某种原因它不再获得语句:
我认为 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_timeout和connection-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()
我们有一个 node.js 应用程序,它通过 pg-promise 连接到 Postgres 11 服务器 - 所有进程都 运行 在 docker 容器中的单个云服务器上。
有时我们会遇到应用程序不再响应的情况。
上次发生这种情况时,我有一点时间通过 pgadmin 检查数据库,它显示连接是 idle in transaction
语句 BEGIN
和独占锁 virtualxid
我觉得情况是这样的:
- 应用程序通过向 db 发送
- db得到这个命令,开始了一个新的事务,从而获得了
virtualxid
模式的独占锁
- 现在 db 等待应用程序发送下一个 statement/s(直到它收到
COMMIT
或ROLLBACK
)- 然后它将释放模式 [= 的独占锁14=] - 但由于某种原因它不再获得语句:
我认为 node.js 事件循环被阻塞了——因为当时,当我们看到这些锁时,node.js 应用程序不再记录语句。但是网络服务器仍然收到请求并报告了一些upstream timed out
请求。
BEGIN
sql 命令开始了一个事务
这有意义吗(我真的不确定 2. 和 3.)?
为什么所有交易一开始都会阻塞?这只是巧合还是显示的 SQL 可能有误?
顺便说一句:在此
事务根本没有阻塞。数据库正在等待应用程序发送下一条语句。
事务 ID 上的锁只是事务相互阻塞的一种技术,即使它们不争用 table 锁(例如,如果它们正在等待行锁):每个事务都在自己的事务 ID 上持有独占锁,如果它必须等待并发事务完成,它可以只请求锁定该事务的 ID(并被阻止)。
如果所有交易看起来都是这样,那么锁一定在您的应用程序中;不涉及数据库。
在查找数据库中阻塞的进程时,在 pg_locks
中查找 granted
为假的行。
你的解释是正确的。至于为什么会这样,那就不好说了。在您的应用程序中似乎存在某种错误(可能是未检测到的死锁),或者可能在 nodes.js 或 pg-promise 中。您将必须在该级别进行调试。
不出所料,问题是由我们的应用程序代码引起的。交易使用不当:
- 其中一个 REST 端点使用 Database.tx() 立即开始新事务。
- 这笔交易被向下传递了多个级别,但是链中的一个函数出错并传递了
undefined
而不是交易到下一级 - 最低存储库级别函数启动了一个新事务(因为事务参数是
undefined
),第二次使用Database.tx()
这在重负载下开始失败:
- 连接池大小设置为 10
- 当有很多同时请求此端点时,我们遇到了 10 个请求开始(打开外部事务)并且尚未到达将请求第二个事务的存储库代码的情况。
- 当这些请求到达存储库代码时,它们会从连接池中请求一个新的(第二个)连接。但是这个调用会阻塞,因为当前所有连接都在使用中。
- 所以我们有一个讨厌的应用程序级死锁
所以解决方案是修复应用程序代码(中间函数必须正确传递事务)。然后一切正常。
此外,我强烈建议设置一个合理的idle_in_transaction_session_timeout和connection-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()