这是《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
处理:
- 统一加锁顺序;
- 缩短事务;
- 批量操作排序;
- 应用捕获并重试;
- 保证幂等。
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';
建议:
- DDL 设置 lock_timeout;
- 应用设置 statement_timeout;
- 治理空闲事务;
- 超时后要有重试和幂等。
18.7 DDL 锁风险
ALTER TABLE 可能需要 AccessExclusiveLock,会等待并阻塞后续查询。危险场景:
ALTER 等待长查询
后续 SELECT 排队
连接池耗尽
处理:
- 低峰变更;
- 终止无关长查询;
- 设置 lock_timeout;
- 使用在线迁移工具;
- 拆分高风险 DDL。
18.8 常见锁问题
| 现象 | 可能原因 |
|---|---|
| INSERT 等待 | 唯一索引冲突 |
| UPDATE 等待 | 同行事务 |
| DDL 卡住 | 长查询持锁 |
| 查询全部排队 | AccessExclusive 等待链 |
| 外键等待 | 被引用行锁 |
| 应用连接耗尽 | 锁等待级联 |
本章小结
锁排查的核心是找出等待链。用 pg_stat_activity、pg_locks 和等待事件判断瓶颈,再通过短事务、排序、超时和低峰 DDL 降低冲突。
思考题
- SELECT 和 ALTER TABLE 的锁冲突如何发生?
- 死锁如何预防?
- lock_timeout 和 statement_timeout 有什么区别?
- 空闲事务如何治理?
- 如何输出阻塞链?