PostgreSQL 的锁机制贯穿在表级访问、行级修改、事务提交等多个层面。当某个事务长时间持锁不放时,其他会话就会被阻塞,表现为接口超时、连接池耗尽甚至整个服务不可用。要排查这类问题,最直接的手段就是查询系统视图 pg_locks。它记录了当前实例上所有活跃锁的信息,包括谁持有什么锁、锁在哪个对象上、有没有会话正在等待。掌握它的用法,等于拿到了数据库内部的锁监控面板。

先理解锁模式:pg_locks 里的 locktype 和 mode 字段
在查询 pg_locks 之前,需要先弄清楚它记录的到底是什么。pg_locks 的每一行代表一个锁请求,既包括已经授予的锁(granted 为 t),也包括正在等待的锁(granted 为 f)。这不是一个只记录持有锁的视图,而是记录所有锁请求的视图,这一点很多人容易误解。
locktype 字段区分了锁的类型。最常见的是 relation(表级锁)、transactionid(事务ID锁)、tuple(行锁提示锁)、virtualxid(虚拟事务ID锁)以及 advisory(咨询锁)。行锁本身并不直接记录在 pg_locks 中,PostgreSQL 的行锁信息存储在行头部的 xmin/xmax 字段里,pg_locks 中的 tuple 类型锁只是一个轻量级的提示锁,用于指示某个事务正在对某行做修改操作。
mode 字段表示锁模式,从弱到强包括 AccessShareLock、RowShareLock、RowExclusiveLock、ShareUpdateExclusiveLock、ShareLock、ShareRowExclusiveLock、ExclusiveLock、AccessExclusiveLock。例如普通的 SELECT 会持有 AccessShareLock,INSERT、UPDATE、DELETE 持有 RowExclusiveLock,而 TRUNCATE、ALTER TABLE、DROP TABLE 会请求 AccessExclusiveLock,这也是最重的锁,会与几乎所有其他锁冲突。
基础查询:直接查看 pg_locks 视图内容
最简单的查询就是直接 select 全表,配合 pg_stat_activity 或系统函数把 pid 关联出来:
-- 查看当前所有锁请求的基本信息
SELECT
locktype,
database,
relation::regclass AS locked_table,
page,
tuple,
virtualxid,
transactionid,
classid,
objid,
objsubid,
virtualtransaction,
pid,
mode,
granted,
fastpath,
waitstart
FROM pg_locks
ORDER BY granted, pid;几个关键字段的含义值得逐个说明。database 是数据库 OID,relation 是被锁对象的 OID,直接转成 ::regclass 可以显示表名,非常方便。virtualtransaction 和 pid 标识了锁属于哪个后端进程。granted 为 true 表示锁已授予,false 表示该会话正在等待这个锁。waitstart(PostgreSQL 14 及以上版本提供)记录了等待开始的时间,可以用来计算锁等待了多久。
注意 pg_locks 是系统视图,任何用户都可以查询,但它反映的是整个实例的瞬时状态,每次查询看到的都是查询执行那一刻的快照。如果锁冲突是间歇性出现的,可以配合定时轮询或者 log_lock_waits 参数记录锁等待日志。
实战:找出谁在阻塞谁,定位阻塞源头
单纯看 pg_locks 只能知道有哪些锁,真正排查问题时需要回答的问题是:哪个会话阻塞了哪个会话,源头是谁。这需要把 pg_locks 和 pg_stat_activity 关联起来。核心思路是:等待中的会话(granted = false)请求的锁,与某个已持锁会话(granted = true)在同一对象上、且模式冲突,那么后者就是阻塞者。
-- 查看被阻塞的会话及其阻塞源头
SELECT
blocked.pid AS blocked_pid,
blocked_act.query AS blocked_query,
blocking.pid AS blocking_pid,
blocking_act.query AS blocking_query,
blocking_act.state AS blocking_state,
now() - blocking_act.xact_start AS blocking_xact_age
FROM pg_locks blocked
JOIN pg_stat_activity blocked_act
ON blocked_act.pid = blocked.pid
JOIN pg_locks blocking
ON blocking.locktype = blocked.locktype
AND blocking.database IS NOT DISTINCT FROM blocked.database
AND blocking.relation IS NOT DISTINCT FROM blocked.relation
AND blocking.transactionid IS NOT DISTINCT FROM blocked.transactionid
AND blocking.pid != blocked.pid
AND blocking.granted
JOIN pg_stat_activity blocking_act
ON blocking_act.pid = blocking.pid
WHERE NOT blocked.granted;拿到阻塞源头的 pid 之后,可以进一步查看它的状态。很多情况下你会发现阻塞者的 state 是 idle in transaction,也就是事务开着却迟迟不提交,这通常是应用代码忘了提交或回滚,是生产环境中最常见的锁问题根源之一。
如果确认某个会话是问题源头,需要终止它,可以调用 pg_terminate_backend 函数。如果只想取消它当前正在执行的 SQL 而保留连接,用 pg_cancel_backend 更温和一些:
-- 取消阻塞会话当前正在执行的语句(连接保留) SELECT pg_cancel_backend(12345); -- 直接终止阻塞会话(强制断开连接,回滚其事务) SELECT pg_terminate_backend(12345); -- 一次性终止所有 idle in transaction 超过 5 分钟的会话 SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE state = 'idle in transaction' AND now() - state_change > interval '5 minutes';
终止会话会回滚它未提交的事务,所有由它持有的锁随之释放,被阻塞的会话会立刻恢复执行。这也是处理锁问题时最立竿见影的手段,但操作前务必确认被终止的会话不是正在执行关键业务的正常事务。
进阶技巧:锁等待链与预防性配置
有时阻塞关系不是简单的一对一,A 等 B、B 等 C,形成等待链。PostgreSQL 9.6 及以上版本在 pg_stat_activity 中提供了 wait_event 和 wait_event_type 字段,wait_event_type 为 Lock 时表示会话正在等锁,配合 wait_event 的具体值(如 transactionid、relation、tuple)可以快速筛选出等锁会话:
-- 查看所有正在等待锁的会话及等待时长
SELECT
pid,
wait_event_type,
wait_event,
now() - query_start AS wait_duration,
state,
left(query, 60) AS query
FROM pg_stat_activity
WHERE wait_event_type = 'Lock'
ORDER BY wait_duration DESC;从事前预防的角度,有几个参数值得关注。lock_timeout 可以设置语句获取锁的最长等待时间,超过就报错回滚,避免请求无限堆积;idle_in_transaction_session_timeout 可以自动终止长时间空闲的事务,从根源上减少锁泄漏;log_lock_waits 设为 on 后,任何超过 deadlock_timeout(默认 1 秒)的锁等待都会记录到日志,方便事后追溯。这三个参数组合使用,能大幅降低锁问题对生产环境的影响。
最后提醒一点,pg_locks 中的锁数据是瞬时的,排查偶发性锁冲突时,建议搭建一个简单的轮询脚本,每隔几秒把 pg_locks 和 pg_stat_activity 的快照落到一张日志表里,问题复现时就能完整还原当时的锁全貌,比事后凭空猜测要可靠得多。
PostgreSQLpg_locks锁查询修改时间:2026-09-06 20:38:42