10.6 观察与诊断并发
并发故障有两个时间尺度:
指标告诉你“何时、影响多大”,catalog 告诉你“现在谁等谁”。杀会话只会改变 live graph,不会自动解释根因。
10.6.1 pg_stat_activity、pg_locks 与等待事件
state 与 wait_event 是两个维度
state='active' 只表示 backend 正在执行 query;它仍可能:
idle in transaction 则没有正在执行 query,却仍持有 transaction、snapshot 和 locks;它常比一条 active query 更危险。
完整 query/session 信息需要适当监控权限,例如受控 pg_read_all_stats;不要给普通应用 superuser。query text、参数、client_addr 可能含敏感数据,证据包应脱敏并设置保留期。
pg_locks 是 lockable object 明细
字段按 locktype 才有意义。relation、transactionid、virtualxid、tuple、advisory、object 等可能共同出现。
row-level lock 的常见观察陷阱:holder 的 row locks 通常不逐行显示在 pg_locks;当另一个 transaction 等该 row 时,它经常表现为等待 holder 的 transaction ID:
所以只搜 locktype='tuple' 会漏掉真实 row blocker。
直接使用 pg_blocking_pids()
手工用 pg_locks 所有 nullable identity columns 做 self join 容易错,也难处理 soft blockers。PostgreSQL 提供:
展开成边:
LEFT JOIN 很重要:prepared transaction 可能成为 blocker 却没有普通 backend activity row。遇到 blocker PID/活动缺失,应同时查 prepared transactions 和 lock catalog,而不是假设采样坏了。
一次采样只是瞬间
短等待可能在两次查询之间消失。实时事件要:
- 设置低成本周期采样或 exporter;
- 保存 UTC timestamp;
- 保留 session identity epoch;
- 关联 log/trace/query id;
- 不因某次 snapshot 为空就否定历史 lock spike;
- 不用高频全字段 query 把监控本身变成压力。
本章屏障让 row-lock edge 停住,便于可靠捕获;生产没有这种配合。
10.6.2 从 Pigsty 定位锁等待与长事务
从影响面缩到 exact graph
在 Pigsty v4.5 的当前仪表盘体系中,可按以下顺序:
仪表盘名称/布局会随版本变化,以当前 Dashboard 文档 为准,不把截图坐标写进 runbook。
常用时间序列包括:
具体 metric/label 以当前 Pigsty Metrics reference 为准。counter 要用 rate/increase 并注意 reset epoch;deadlocks=0 的瞬时值不能代表历史从未发生。
先固定四个维度
调查窗口至少固定:
cls/ins:哪个 cluster/instance,primary 还是 replica;datname:哪个 database;- UTC time range:与用户错误/发布窗口对齐;
- query/application identity:谁受影响、谁可能持锁。
然后回答:
看到 lock count 高不一定是问题:已 granted 的非冲突 locks 很正常。重点是 ungranted wait、阻塞时长、队列扩散和用户 SLI。
40001 不一定在 lock 面板出现
SSI SIReadLock 不造成常规 blocking;serialization failure 可能没有一条长 lock wait 曲线。需要 application/driver 暴露 SQLSTATE 40001 和 retry attempts,并关联:
- transaction family;
- abort/success rate;
- active connections;
- transaction duration;
- query plan/predicate lock 粒度;
- hot key/tenant;
- deploy/version。
同理,deadlock victim 很快被 abort,live graph 已消失;pg_db_deadlocks 与 PostgreSQL log 才保留历史。
transaction age 是放大器
长 transaction:
- 持锁更久;
- 保留 old snapshot;
- 增加 SSI overlap;
- 阻碍 vacuum cleanup;
- 放大 WAL/replication/DDL 等待;
- 让 retry 代价更大。
因此并发性能优化常常不是改 lock mode,而是把 remote call、用户思考、巨大 batch 移出 transaction,并治理 pool 中 idle in transaction。
10.6.3 保存阻塞图,而不是先杀会话
动作前证据包
最小 live artifact:
本章由并发协调器生成的
row-lock-graph.csv,一次关系是:
holder 故意等教学 barrier;生产则要问 holder 为何尚未 commit。
找 root blocker,而非随便处理叶子
取消 waiter 只减少一个症状,root blocker 仍可能阻塞几十个请求。应把 graph 沿边向上追到:
再按影响与业务 owner 决策。root 也可能是正在执行必须完成的财务事务、migration 或恢复操作;“阻塞最多”不自动等于“应该杀”。
cancel 与 terminate 不同
请求取消当前 query。若 session 在显式 transaction 中,query error 会使 transaction failed,但 client 若不 rollback,仍可能继续占用连接/某些事务资源。
终止整个 backend,未提交 transaction rollback,client 断开。它的影响更大,可能触发应用 retry storm 或留下外部副作用未知状态。
执行前必须重新验证 PID epoch,避免 PID reuse:
还要确认:
- 是否为 autovacuum/background/replication/system backend;
- transaction rollback 的业务影响;
- application 是否会自动 retry;
- external effect 是否 commit-unknown;
- 是否有 owner/incident approval;
- 动作后怎样验收 graph 与数据不变量。
自动化绝不能按 xact_start 最老或 application name 模糊匹配批量 kill。
处理后仍要解释根因
完成止血后保存 after:
再修:
- transaction scope;
- lock order;
- missing index 导致访问过多 rows;
- queue claim;
- external call in transaction;
- timeout/retry storm;
- DDL 发布方式;
- leaked pool connection;
- missing idempotency。
“杀掉 blocker,图空了”只是动作成功,不是问题解决。
延伸阅读
- PostgreSQL 18:Monitoring Database Activity
- PostgreSQL 18:Viewing Locks
- PostgreSQL 18:
pg_blocking_pids - Pigsty:Dashboard
- Pigsty:Metrics
上一节:咨询锁与跨行协调 · 返回本章目录 · 下一节:实战:库存扣减与支付幂等 · 查看全书目录 · 查看索引中心