PostgreSQL的死锁是指两个或多个事务互相持有对方需要的锁,且都在等待对方释放锁,导致所有相关事务都无法继续执行的情况。死锁发生后PostgreSQL会自动检测并终止其中一个事务,返回死锁错误,但我们需要定位具体原因避免问题重复出现。

死锁产生的常见原因
PostgreSQL的死锁大多和事务执行顺序、锁类型冲突有关,常见场景包括:
- 多个事务以不同顺序更新同一批数据,比如事务A先更新表1再更新表2,事务B先更新表2再更新表1
- 长事务持有锁时间过长,阻塞了其他事务的锁请求
- 对大表执行全表更新时,锁范围覆盖过广,和其他小事务的锁产生冲突
- 索引缺失导致更新操作触发表锁升级,和其他行级锁冲突
死锁排查定位完整流程
第一步:查看数据库死锁日志
PostgreSQL默认会把死锁相关信息记录到日志中,首先可以查看数据库日志定位基础信息。日志中会出现deadlock detected关键字,包含死锁涉及的事务ID、等待的锁类型、相关SQL片段。如果是Linux环境,可以通过以下命令快速筛选死锁日志:
grep "deadlock detected" /var/log/postgresql/postgresql-14-main.log
日志中典型死锁内容示例如下:
2024-05-10 14:23:10 CST [12345] ERROR: deadlock detected 2024-05-10 14:23:10 CST [12345] DETAIL: Process 12345 waits for ShareLock on transaction 67890; blocked by process 54321. Process 54321 waits for ShareLock on transaction 12345; blocked by process 12345. Process 12345: UPDATE user_info SET balance = balance - 100 WHERE id = 1; Process 54321: UPDATE user_info SET balance = balance + 100 WHERE id = 2;
第二步:查询实时活跃会话信息
如果死锁正在发生,可以通过pg_stat_activity系统视图查询当前所有活跃会话的状态,定位阻塞关系。执行以下SQL可以查看当前等待锁的会话:
SELECT
pid,
usename,
datname,
query,
state,
wait_event_type,
wait_event,
now() - query_start AS query_duration
FROM pg_stat_activity
WHERE wait_event_type = 'Lock'
ORDER BY query_start ASC;
结果中wait_event_type为Lock的会话就是正在等待锁的会话,query字段是对应的执行SQL,pid是会话进程ID。如果要查看某个会话被哪个会话阻塞,可以结合pg_locks视图查询:
SELECT
a.pid AS blocked_pid,
a.usename AS blocked_user,
a.query AS blocked_query,
b.pid AS blocking_pid,
b.usename AS blocking_user,
b.query AS blocking_query
FROM pg_stat_activity a
JOIN pg_locks l1 ON a.pid = l1.pid AND NOT l1.granted
JOIN pg_locks l2 ON l1.locktype = l2.locktype
AND l1.database IS NOT DISTINCT FROM l2.database
AND l1.relation IS NOT DISTINCT FROM l2.relation
AND l1.page IS NOT DISTINCT FROM l2.page
AND l1.tuple IS NOT DISTINCT FROM l2.tuple
AND l1.virtualxid IS NOT DISTINCT FROM l2.virtualxid
AND l1.transactionid IS NOT DISTINCT FROM l2.transactionid
AND l2.granted
JOIN pg_stat_activity b ON b.pid = l2.pid
WHERE a.wait_event_type = 'Lock';
第三步:定位具体死锁事务和SQL
结合日志中的事务ID和实时会话的PID,可以定位到具体的死锁SQL。如果需要终止阻塞的会话,可以使用pg_terminate_backend函数,注意优先终止执行时间更长、影响范围更小的会话:
-- 终止PID为54321的会话,替换为实际查询到的阻塞会话PID SELECT pg_terminate_backend(54321);
第四步:复现和分析死锁根因
拿到死锁相关的SQL后,需要分析事务的执行顺序和锁类型。可以通过EXPLAIN分析SQL的执行计划,确认是否触发表锁、是否走了正确的索引。如果是执行顺序导致的死锁,可以调整事务中SQL的执行顺序,保证所有事务都按照相同的顺序操作数据。
避免PostgreSQL死锁的实用建议
- 尽量缩短事务的执行时间,避免长事务持有锁过久
- 所有事务按照统一的顺序访问表和行数据,避免交叉加锁
- 为更新操作涉及的字段添加合适的索引,避免全表扫描触发表锁升级
- 更新数据时尽量指定具体的行范围,减少锁的覆盖范围
- 可以设置合理的
lock_timeout参数,避免事务无限等待锁
总结
PostgreSQL死锁的排查定位核心是先通过日志获取基础信息,再通过系统视图查询实时会话的锁等待关系,最终定位到具体的SQL和事务。日常开发中遵循统一的操作顺序、控制事务时长,可以有效减少死锁的发生概率。如果遇到复杂死锁问题,还可以开启log_lock_waits参数,记录更详细的锁等待信息辅助排查。
PostgreSQL死锁排查死锁诊断pg_stat_activity修改时间:2026-06-18 06:48:21