查 pg_stat_activity 是定位 PostgreSQL 长事务阻塞的唯一可靠入口,重点筛选 state='active' 且 now()-xact_start>5min 的记录,结合 pg_locks 关联识别持锁者与等待者,再谨慎 kill 并优先预防。
查 pg_stat_activity 看谁在长时间运行
postgresql 里没有“表锁状态”的独立视图,真正卡住别人的,往往是某个事务没提交、还占着行锁或表锁,其他查询在等它。关键不是查“锁”,而是找那个迟迟不结束的事务。
pg_stat_activity 是唯一靠谱的入口——它实时反映每个连接在干什么、干了多久、有没有卡住。
- 重点看
state = 'active'且backend_start和state_change时间差很大(比如超过几分钟)的行 -
wait_event_type = 'Lock'表示这个进程正在等锁(大概率是被别人堵了) -
backend_type = 'client backend'排除系统内部进程,聚焦真实用户连接 - 用
now() - xact_start算事务持续时间,比只看backend_start更准(因为可能空闲很久才开始事务)
SELECT pid, usename, datname, client_addr,
now() - xact_start AS xact_duration,
state, wait_event_type, wait_event, query
FROM pg_stat_activity
WHERE state = 'active'
AND now() - xact_start > interval '5 minutes'
ORDER BY xact_duration DESC;用 pg_locks 关联找出谁锁了谁
单看 pg_stat_activity 只知道“有人在等”,但不知道等的是谁。得把 pg_locks 拉进来做关联,才能定位阻塞源头。
核心逻辑:一个进程的 pid 出现在别人 pg_locks 的 granted = false 记录里,同时又在另一条 granted = true 记录里——它就是持锁者。
-
pg_locks里locktype = 'relation'对应表级锁,'row'是行锁,但多数阻塞其实来自事务级排他(AccessExclusiveLock) - 必须用
database和relation字段过滤到具体库和表,否则锁太多干扰判断 - 别直接查
pg_locks全表——没关联pg_stat_activity就是一堆无意义的数字
SELECT blocked.pid AS blocked_pid,
blocker.pid AS blocker_pid,
blocked.query AS blocked_query,
blocker.query AS blocker_query,
blocked.xact_start AS blocked_xact_start,
blocker.xact_start AS blocker_xact_start
FROM pg_stat_activity blocked
JOIN pg_locks bl ON blocked.pid = bl.pid AND NOT bl.granted
JOIN pg_locks blr ON blr.locktype = bl.locktype
AND blr.database IS NOT DISTINCT FROM bl.database
AND blr.relation IS NOT DISTINCT FROM bl.relation
AND blr.page IS NOT DISTINCT FROM bl.page
AND blr.tuple IS NOT DISTINCT FROM bl.tuple
AND blr.virtualxid IS NOT DISTINCT FROM bl.virtualxid
AND blr.transactionid IS NOT DISTINCT FROM bl.transactionid
AND blr.classid IS NOT DISTINCT FROM bl.classid
AND blr.objid IS NOT DISTINCT FROM bl.objid
AND blr.objsubid IS NOT DISTINCT FROM bl.objsubid
AND blr.pid != bl.pid
JOIN pg_stat_activity blocker ON blocker.pid = blr.pid
WHERE blocker.state = 'active';KILL 前先确认是不是应用层 bug
拿到 pid 后别急着 pg_terminate_backend()。很多长事务不是人为误操作,而是应用没正确处理异常、没关闭事务,或者 ORM 自动开启但忘了 commit。
- 先看
query字段——如果是UPDATE ... WHERE id = ?卡住,可能是索引缺失导致全表扫描;如果是空 query 但state = 'idle in transaction',基本是代码漏了 commit/rollback - 检查
client_addr和usename,确认是不是你负责的服务,避免误杀运维或监控连接 -
pg_terminate_backend(pid)会立刻中断连接、回滚事务,但频繁这么做只是掩盖问题,不是解决 - 真要 kill,优先用
pg_cancel_backend(pid)(尝试取消当前语句),只在它无效时再上terminate
预防比排查更省事
锁问题反复出现,说明数据库或应用层有隐患。光靠查 pg_stat_activity 是被动救火。
- 给应用加事务超时:例如 Spring 的
@Transactional(timeout = 30),或 JDBC URL 加tcpKeepAlive=true&socketTimeout=30 - 禁止手动开启长事务:DBA 层面可设
idle_in_transaction_session_timeout = '5min',自动断开空闲事务 - 慢查询必须走索引:
EXPLAIN ANALYZE跑一跑卡住的query,90% 的阻塞根源其实是没走索引的 UPDATE/DELETE - 不要在事务里做 HTTP 调用、文件读写、sleep——这些会让锁持有时间不可控
最常被忽略的一点:pg_stat_activity 默认只保留当前连接信息,如果连接断了就看不到历史。想追查已消失的长事务,得提前打开 log_min_duration_statement 并配合日志分析。










