← 中文资源

简体中文版本

排查 PostgreSQL 的 idle in transaction 会话

找出开着事务却什么也不做的会话,度量其存活时长与锁足迹,并从应用层修掉泄漏点。

阅读英文原文

目标

idle in transaction 的会话一边持锁不干活,一边阻止 vacuum 清理死元组,还会拉长快照年龄让查询越来越臃肿。这种状态在 pg_stat_activity 里一目了然,而根因几乎总是应用代码在 BEGIN 与 COMMIT 之间提前返回。本食谱教你按存活时长排序找出最老的会话、查看其锁足迹、定位到具体服务与函数,最后加一层服务端兜底超时。

步骤要点

  1. 查询 pg_stat_activity 中 state 为 idle in transaction 的会话,按 xact_start 排序,最老的排在最前,通常就是阻塞众人的元凶。
  2. 把最老会话的 pid 关联 pg_locks 与 pg_class,列出它持有的行锁与表锁,确认热点表被 RowExclusiveLock 卡住。
  3. 查该 pid 的 application_name、client_addr 与 backend_start,锁定是哪个服务实例哪个部署。
  4. 在应用代码中排查提前 return 导致事务未收尾的路径,并用 withTx 辅助函数保证任何路径都走 COMMIT 或 ROLLBACK。
  5. 设置 idle_in_transaction_session_timeout 为 5 分钟并 reload 配置,让服务端充当烟雾报警器,泄漏会话被杀时客户端报错堆栈正好指出泄漏函数。

排错提示

会话在你检查前就消失,就用循环轮询 pg_stat_activity 落表,下次复现时留下完整上下文;连接还回连接池不会结束事务,release 前必须显式 COMMIT/ROLLBACK 或用封装函数兜底;ORM 隐藏事务边界时去找开工作单元的回调或装饰器,超时报错的堆栈会直接点名泄漏函数。

适用边界

超时兜底是杀连接,客户端会看到报错,它是绊网而不是修复本身;查锁需要活跃会话的 pid,阻塞者若已退出只能靠锁日志等历史手段。

代码示例

-- Who is idle, and for how long?
SELECT pid,
       now() - xact_start AS xact_age,
       application_name,
       left(query, 50) AS last_query
FROM pg_stat_activity
WHERE state = 'idle in transaction'
ORDER BY xact_start
LIMIT 10;

--   pid  | xact_age  | application_name | last_query
--  48213 | 03:21:07  | orders-api-v3     | SELECT balance FROM accounts ...

-- Stop the bleeding, then fix the code
ALTER SYSTEM SET idle_in_transaction_session_timeout = '5min';
SELECT pg_reload_conf();