一、PostgreSQL 死锁的定义
当多个事务相互等待对方释放锁资源时,形成循环依赖,导致所有事务无法继续执行,即为死锁。PostgreSQL 默认启用死锁检测机制,通常在 1 秒(deadlock_timeout
)内自动检测并回滚其中一个事务以解除死锁。但复杂场景仍需人工干预。
二、查看死锁的方法
1. 通过数据库日志定位
PostgreSQL 日志(默认 postgresql.log
)会记录死锁详情,包含以下关键信息:
DETAIL: Process <pid1> waits for ShareLock on transaction <txid1>; blocked by process <pid2>.
Process <pid2> waits for ShareLock on transaction <txid2>; blocked by process <pid1>.
此日志表明进程 pid1
和 pid2
相互阻塞,形成死锁
2. 使用系统视图查询
- 查看阻塞中的进程:
SELECT * FROM pg_stat_activity
WHERE waiting = 't' OR state = 'active';
结果中的 pid
为被阻塞的进程 ID,query
显示当前执行的 SQ
- 关联锁与表信息:
SELECT a.locktype, a.pid, a.mode, b.relname
FROM pg_locks a
JOIN pg_class b ON a.relation = b.oid
WHERE b.relname = 'your_table';
此查询显示特定表上的锁类型、持有进程及模式(如 ExclusiveLock
)
三、解决死锁的步骤
1. 终止阻塞进程
- 取消单条查询:
SELECT pg_cancel_backend(pid); -- 仅终止查询,保留事务
- 强制终止进程:
SELECT pg_terminate_backend(pid); -- 终止整个会话
通过 pg_stat_activity
获取目标 pid
后执行
2. 预防与优化策略
- 事务设计:
- 统一操作顺序:确保事务按固定顺序访问资源(如先更新表 A 再表 B)
- 短事务原则:减少锁持有时间,避免长时间事务
- 索引优化:
- 为高频查询字段添加索引,减少全表扫描导致的锁竞争
- 参数调整:
- 设置
lock_timeout
:限制单次锁等待时间,超时自动回滚。 - 降低隔离级别:如使用
READ COMMITTED
替代SERIALIZABLE
四、典型案例分析
场景描述
事务 A 先更新 user
表后更新 product
表,事务 B 反之,导致相互等待
解决方案
- 日志定位:通过日志确认死锁涉及的进程和表。
- 终止进程:使用
pg_terminate_backend()
终止优先级低的事务。 - 代码调整:统一事务操作顺序,如所有事务均按
user → product
顺序执行。
五、工具与监控建议
- 实时监控:使用
pgAdmin
或pg_stat_activity
监控活跃会话。 - 自动化脚本:定期检查
pg_locks
并发送告警。
通过以上方法,可有效定位并解决 PostgreSQL 死锁问题,同时通过优化设计降低死锁概率。如需进一步排查复杂死锁,建议结合 EXPLAIN
分析查询计划。