一、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>.

此日志表明进程 pid1pid2 相互阻塞,形成死锁

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 反之,导致相互等待

解决方案

  1. 日志定位:通过日志确认死锁涉及的进程和表。
  2. 终止进程:使用 pg_terminate_backend() 终止优先级低的事务。
  3. 代码调整:统一事务操作顺序,如所有事务均按 user → product 顺序执行。


五、工具与监控建议

  • 实时监控:使用 pgAdminpg_stat_activity 监控活跃会话。
  • 自动化脚本:定期检查 pg_locks 并发送告警。


通过以上方法,可有效定位并解决 PostgreSQL 死锁问题,同时通过优化设计降低死锁概率。如需进一步排查复杂死锁,建议结合 EXPLAIN 分析查询计划。