PostgreSQLNotes

第 18 章:锁与等待事件

zjc 于 2026-01-18 发布

这是《PostgreSQL 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 锁问题常表现为 SQL 突然变慢或挂起。PostgreSQL 提供锁视图和等待事件,帮助定位“谁阻塞谁”。

18.1 锁类型

锁模式 典型操作
AccessShare SELECT
RowShare SELECT FOR UPDATE / SHARE
ShareUpdateExclusive VACUUM、ANALYZE
Share CREATE INDEX
ShareRowExclusive 某些约束触发器
Exclusive 某些维护操作
AccessExclusive DROP、TRUNCATE、VACUUM FULL、ALTER

兼容矩阵可查询官方文档。冲突越强,对并发影响越大。

18.2 行锁

BEGIN;
SELECT * FROM orders WHERE id = 1 FOR UPDATE;
UPDATE orders SET status = 'PAID' WHERE id = 1;
COMMIT;

行锁信息存在行版本中,不放在内存锁表。

18.3 死锁

事务 A:锁 row1,等待 row2
事务 B:锁 row2,等待 row1

PostgreSQL 检测后终止一个事务:

ERROR: deadlock detected

处理:

  1. 统一加锁顺序;
  2. 缩短事务;
  3. 批量操作排序;
  4. 应用捕获并重试;
  5. 保证幂等。

18.4 查看锁等待

SELECT
    blocked.pid AS blocked_pid,
    blocked.query AS blocked_query,
    blocking.pid AS blocking_pid,
    blocking.query AS blocking_query
FROM pg_stat_activity blocked
JOIN pg_locks bl
  ON bl.pid = blocked.pid AND NOT bl.granted
JOIN pg_locks ul
  ON ul.locktype = bl.locktype
 AND ul.database IS NOT DISTINCT FROM bl.database
 AND ul.relation IS NOT DISTINCT FROM bl.relation
 AND ul.granted
JOIN pg_stat_activity blocking
  ON blocking.pid = ul.pid
WHERE blocked.wait_event_type = 'Lock';

18.5 等待事件

SELECT
    pid,
    wait_event_type,
    wait_event,
    state,
    query
FROM pg_stat_activity
WHERE state <> 'idle';

常见事件:

类型 含义
Lock 等待锁
IO 等待磁盘
LWLock 轻量锁或内部共享结构
Client 等待客户端
IPC 等待进程间通信
Timeout 等待超时

18.6 锁超时

SET lock_timeout = '3s';
SET statement_timeout = '30s';
SET idle_in_transaction_session_timeout = '10min';

建议:

  1. DDL 设置 lock_timeout;
  2. 应用设置 statement_timeout;
  3. 治理空闲事务;
  4. 超时后要有重试和幂等。

18.7 DDL 锁风险

ALTER TABLE 可能需要 AccessExclusiveLock,会等待并阻塞后续查询。危险场景:

ALTER 等待长查询
后续 SELECT 排队
连接池耗尽

处理:

  1. 低峰变更;
  2. 终止无关长查询;
  3. 设置 lock_timeout;
  4. 使用在线迁移工具;
  5. 拆分高风险 DDL。

18.8 常见锁问题

现象 可能原因
INSERT 等待 唯一索引冲突
UPDATE 等待 同行事务
DDL 卡住 长查询持锁
查询全部排队 AccessExclusive 等待链
外键等待 被引用行锁
应用连接耗尽 锁等待级联

本章小结

锁排查的核心是找出等待链。用 pg_stat_activitypg_locks 和等待事件判断瓶颈,再通过短事务、排序、超时和低峰 DDL 降低冲突。

思考题

  1. SELECT 和 ALTER TABLE 的锁冲突如何发生?
  2. 死锁如何预防?
  3. lock_timeout 和 statement_timeout 有什么区别?
  4. 空闲事务如何治理?
  5. 如何输出阻塞链?