如何用pgrowlocks查询PostgreSQL行锁信息?

来源:NoSQL教程作者:李修然头衔:网络博主
导读:本期聚焦于李修然创作的《如何用pgrowlocks查询PostgreSQL行锁信息?》,敬请观看详情。pgrowlocks扩展通过扫描目标表的物理堆文件,逐行读取行头中的锁标记位,再把行锁类型、持有事务号、后端进程号等信息汇总返回。这个过程并不依赖事务等待队列,因此能够显示pg_locks无法直观呈现的行级锁细节。当多个会话对同一行执行UPDATE或SELECT FOR UPDATE时,仅仅查看pg_locks往往只能看到关系锁和事务锁,无法确定具体是哪一行被谁持有。pgrowlocks正好补上这一环。它返回的locked_row列是元组TID,可以直接配合ctid定位物理行;xids与pids数组则分别指向持锁事务和后端进程,方便关联pg_stat_activity进一步排查。需要注意的是,该扩展需要显式创建,且在并发较高或表很大的情况下会带来一定扫描开销,更适合在阻塞发生后进行精确诊断。

在PostgreSQL中排查锁等待时,经常会先查询pg_locks视图,但该视图侧重对象级锁和事务号锁,难以直接告诉你是哪一行被什么事务锁定。当业务出现两个事务同时更新同一条记录导致卡住时,pgrowlocks扩展可以从物理行角度给出答案。它读取表的堆存储结构,逐行返回当前可见的行锁信息,帮助定位锁竞争的具体位置。

如何用pgrowlocks查询PostgreSQL行锁信息?

行级锁在PostgreSQL中不会像表级锁那样完整地记录在共享内存的锁管理器中,而是以状态标记的形式写入每一行元组的头部。pg_locks虽然能看到一些tuple锁和transactionid锁,但无法映射到具体的行TID。pgrowlocks正是为了弥补这个盲区而设计的官方扩展模块。

如何安装pgrowlocks扩展

pgrowlocks属于PostgreSQL源码自带的contrib扩展,如果你的环境中安装了完整发行包,通常可以直接创建,无需额外下载。创建命令非常简单:

CREATE EXTENSION pgrowlocks;

执行后会增加一个名为pgrowlocks的函数,函数签名可以理解为pgrowlocks(relname text) returns setof record。使用时需要传入目标表的名称,例如SELECT * FROM pgrowlocks('inventory');。这里的表名是普通字符串参数,因此必须注意表达式中的单引号用法。函数返回一组记录,每条记录对应堆文件中被行锁标记影响的一行。

删除扩展也很容易,直接执行DROP EXTENSION pgrowlocks;即可。扩展中的C函数会访问表的物理存储,因此要求调用者对目标表有读取权限,否则会因权限不足而报错。通常普通业务账号拥有表的SELECT权限时就可以正常查询。

如果不想在数据库中创建扩展,也可以只调用函数一次试试,但要明白pgrowlocks不是内置视图,必须先创建扩展才能使用。这个函数的好处是不需要修改表结构,也不会留下后台常驻进程,只在被调用时进行一次扫描。

解读pgrowlocks返回字段

先创建一个测试表并插入两行数据,然后在一个事务中对其中一行加锁。

CREATE TABLE inventory (
  item_id integer PRIMARY KEY,
  quantity integer NOT NULL
);

INSERT INTO inventory VALUES (1, 100), (2, 200);

在会话一中开启事务并锁定一行:

BEGIN;
SELECT * FROM inventory WHERE item_id = 1 FOR UPDATE;

这时保持事务不提交,然后在同一会话或另一个会话中查询pgrowlocks:

SELECT * FROM pgrowlocks('inventory');

返回结果可能类似下面这样:

 locked_row | locker | multi |   xids    |       modes       |  pids
------------+--------+-------+-----------+-------------------+-------
 (0,1)      |   1234 | f     | {1234}    | {"For Update"}    | {5678}

其中locked_row是元组TID,括号中的两个数字分别表示块号和块内偏移。locker是当前持有行锁的事务号。multi为假时表示这是普通单一事务锁,xids数组里只有一个事务号。modes显示锁模式,这里是For Update;如果使用SELECT FOR SHARE,模式会显示为For Sharepids是持有该行锁的后端进程号,方便关联pg_stat_activity查看具体会话。

如果同一行被多个事务以共享模式锁定,或者既有共享锁又有更新锁,PostgreSQL会使用MultiXact机制管理多个事务对同一行的锁状态。此时multi值为真,xidsmodespids都会是数组,数组中的元素按照多事务成员顺序展开。例如两个会话都对同一行执行FOR SHARE,查询pgrowlocks时就能看到这一行同时被两个事务持有共享锁,这样就不会误判为只有一个持锁者。

pg_locks视图记录的是锁管理器中的锁,行级锁并不在其中。因此想确认行锁细节时,pgrowlocks比pg_locks更直接。不过需要注意,pgrowlocks展示的是当前已经持有的行锁,如果某个事务正在等待获取同一行的更新锁,但还没有真正获得锁,那么它在pgrowlocks中不会以等待者身份出现,这类信息需要结合pg_stat_activity的等待事件和pg_locks中的事务锁来判断。

用pgrowlocks定位行锁阻塞

典型场景是应用日志中出现事务卡住,某个更新语句长时间不返回。可以先用以下语句找到等待中的会话:

SELECT pid, state, wait_event_type, wait_event, query
FROM pg_stat_activity
WHERE state IS NOT NULL
  AND pid <> pg_backend_pid();

如果发现某个会话的等待事件是transactionid,说明它在等待另一个事务结束。接下来要确定到底等的是哪一行,就需要查询pgrowlocks。假设等待发生在inventory表,可以执行:

SELECT locked_row, locker, xids, modes, pids
FROM pgrowlocks('inventory');

拿到locked_row后,就能用TID直接查询该行内容,例如SELECT * FROM inventory WHERE ctid = '(0,1)';。结合pids数组可以找到正在持有锁的后端进程,再通过pg_stat_activity查看它的事务开始时间、最后执行的语句,判断要不要终止该进程。终止进程可以使用SELECT pg_cancel_backend(pid);SELECT pg_terminate_backend(pid);,前者更温和,后者更彻底。

在多事务共享锁的场景中,一个行锁可能对应多个后端进程。例如多个只读事务都对同一行执行FOR SHARE,它们彼此不会阻塞,却会阻塞后续试图执行FOR UPDATE的会话。此时pgrowlocks返回的pids数组包含所有共享锁持有者,排查时不能只关注某一个进程,需要结合事务开始时间和应用日志决定是否批量终止。

除了排查阻塞,pgrowlocks还可以用来审计应用程序的事务行为。比如业务代码中无意识地使用了SELECT FOR UPDATE,导致热点行长期被锁定。通过定时采集pgrowlocks结果,可以发现哪些行频繁出现锁,从而反向优化事务设计,缩小锁定范围或改用乐观并发控制。

实现原理与使用注意事项

pgrowlocks的C代码位于PostgreSQL源码的contrib/pgrowlocks/pgrowlocks.c文件中。它的主要逻辑是调用堆扫描接口逐块读取目标表的数据页,对每一行元组检查头部信息。PostgreSQL在元组头中维护了多个锁相关标记位,包括HEAP_XMAX_LOCK_ONLYHEAP_XMAX_EXCL_LOCKHEAP_XMAX_SHR_LOCK等。pgrowlocks根据这些标记判断行是否被锁定,以及属于排他锁还是共享锁。

如果锁标记指向MultiXact,pgrowlocks会进一步调用多事务管理函数获取成员事务号和对应的锁模式。这个过程中需要读取多事务段文件,因此查询pgrowlocks涉及的基础操作比普通SQL更底层。函数最终把每一行的TID、锁持有者信息组织成记录返回给客户端。由于它是一次完整表扫描,表越大查询耗时越长。

在生产环境中使用pgrowlocks时要特别注意性能开销。对于几千万行的大表,执行一次SELECT * FROM pgrowlocks('big_table');可能扫描所有数据页并解析每行头信息,可能持续数秒甚至更久。建议在已经锁定问题范围的情况下,只对相关表执行查询,避免在业务高峰期频繁调用。此外,扫描期间可能读到未提交数据,行锁信息与实际事务状态之间可能存在瞬时差异,因此结果应作为诊断线索,而不是严格的锁管理器快照。

从PostgreSQL版本演进来看,pgrowlocks的思路也被一些监控工具借鉴,比如用于展示行锁等待拓扑。对于无法创建扩展的环境,还可以使用pageinspect扩展直接查看页内元组头信息,但操作更加底层,远不如pgrowlocks方便。掌握pgrowlocks的输出字段和查询方法,能够显著提升定位行锁阻塞的效率。

PostgreSQL行锁pgrowlocks锁等待诊断修改时间:2026-08-20 17:21:46

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。