PostgreSQL死锁日志需重点定位互锁的进程PID、事务ID、锁对象及具体SQL;结合pg_stat_activity查活跃会话与query,确认未提交事务;索引不能避免所有死锁,锁范围受隔离级别、执行计划影响;修复关键在于统一所有业务路径的加锁顺序。
deadlock detected 错误日志PostgreSQL 在发生死锁时会主动中止其中一个事务,并在日志里输出带 deadlock detected 的错误行。关键不是“有没有报错”,而是要从日志里快速定位哪两个(或多个)事务、哪几条 SQL、锁在了哪些对象上。
典型日志片段长这样:
ERROR: deadlock detected DETAIL: Process 12345 waits for ShareLock on transaction 67890; blocked by process 6789. Process 6789 waits for ShareLock on transaction 12345; blocked by process 12345. HINT: See server log for query details. CONTEXT: while updating tuple (123,45) in relation "orders"
Process 12345 和 Process 6789 是操作系统级的 backend PID,可结合 pg_stat_activity 查当前会话transaction 67890 和 transaction 12345 是内部事务 ID,不能直接查,但能说明循环等待发生在两个事务之间ShareLock on transaction ... 表示是事务级锁冲突(常见于 SERIALIZABLE 隔离级别),不是行锁或表锁;若看到 RowExclusiveLock on relation "xxx",则是普通 DML 引发的行级死锁while updating tuple (123,45) in relation "orders" 是最实用的线索:说明卡在更新 orders 表第 123 页第 45 行——结合表结构和索引,基本能反推出被锁的主键或唯一键值单看错误日志不够,必须立刻查 pg_stat_activity 抓快照,尤其是死锁发生前后 10 秒内的活跃会话。重点过滤 state = 'active' 或 state = 'idle in transaction' 的记录,并按 backend_pid 匹配日志里的进程号。
SELECT pid, usename, application_name, client_addr, backend_start, state_change, query FROM pg_stat_activity WHERE pid IN (12345, 6789); 查对应会话正在执行的 query
state_change 时间戳是否接近死锁日志时间——若差几秒内,大概率就是它query 显示为 或截断严重,说明语句已执行完但事务未提交,此时要看 backend_start 和 xact_start 判断事务挂了多久
COMMIT 或 ROLLBACK,这是死锁高频诱因很多人以为“只要 WHERE 条件走索引,就只锁匹配行”,但 PostgreSQL 的锁行为受隔离级别、语句类型、执行计划共同影响。例如:
UPDATE orders SET status = 'shipped' WHERE user_id = 123; —— 若 user_id 没有索引,会全表扫描并锁住所有扫描过的行(甚至页),大幅增加死锁概率user_id 有索引,若优化器选了 Bitmap Heap Scan + Recheck,也可能临时锁住非目标行SELECT ... FOR UPDATE 在可重复读下会锁住所有扫描到的可见行(包括已删除但未清理的 dead tuple),而不仅限于最终返回结果UPDATE ... WHERE id IN (1,2,3,4,5),若这些 id 在物理存储上不连续,锁顺序可能和应用执行顺序不一致,形成环路死锁无法靠日志“修好”,必须在测试环境模拟相同访问模式。不要依赖随机压测,要精准控制事务顺序:
UPDATE t1 再 UPDATE t2,会话 B 反过来先 UPDATE t2 再 UPDATE t1
UPDATE 后加 SELECT pg_sleep(0.1); 延迟,放大竞争窗口pg_locks 中锁的持有顺序是否统一:所有事务都按 t1 → t2 → t3 的固定顺序加锁,就能彻底避免环路FOR UPDATE 或自动拆分批量更新(如 Django 的 bulk_update 在某些版本会逐条执行)真正难的不是识别死锁,而是确认所有业务路径下的锁顺序是否收敛——尤其当微服务间通过消息队列异步触发 DB 操作时,锁顺序很容易被拆散。