← 返回题目列表

PostgreSQL 锁机制和死锁如何理解?如何排查锁等待?

高频 中等 第 11 / 31 题 更新于 2026/07/29
PostgreSQL死锁pg_locks

简化版

PostgreSQL 有表级锁、行级锁和事务相关锁。普通更新会加行锁,DDL 往往需要更强的表锁;死锁通常来自多个事务以不同顺序持有资源。排查时看 pg_stat_activitypg_locks 和阻塞链,治理重点是缩短事务、统一加锁顺序、避免长事务和高峰 DDL。

详细版

锁的目标是保证并发正确性。PostgreSQL 的 MVCC 让读写很多时候互不阻塞,但写写冲突、DDL、外键检查、显式锁仍会产生等待。

排查思路:

  • 查看当前活动 SQL 和等待事件。
  • pg_locks 找锁类型和持有者。
  • 识别阻塞者和被阻塞者。
  • 优先处理长事务和空闲事务。
  • 对死锁,数据库会自动检测并中止其中一个事务。

治理方法包括控制事务长度、固定资源访问顺序、为外键列建索引、把 DDL 放低峰期执行。

完整版教学

一、MVCC 减少读写阻塞,但不等于没有锁

PostgreSQL 通过 MVCC 保存行版本,让普通 select 通常不阻塞 updateupdate 也不阻塞历史快照读取。这是它并发能力的重要来源。

但这不代表 PostgreSQL 没有锁。两个事务同时修改同一行,仍然会产生行级锁等待;DDL 修改表结构时,也可能等待表上其他事务结束。

事务 A: update account set balance = balance - 100 where id = 1;
事务 B: update account set balance = balance + 50  where id = 1;
事务 B 必须等待事务 A 提交或回滚

记忆钩子:MVCC 主要减少读写冲突,写写冲突和结构变更仍然靠锁协调。

二、表级锁常见于 DDL 和显式 LOCK

PostgreSQL 有多种表级锁模式,不同 SQL 会申请不同强度的锁。普通查询也会有轻量的访问锁,DDL 通常需要更强的锁。

比如 ALTER TABLE 可能需要阻塞写入甚至读取,具体取决于操作类型。生产环境在大表上执行 DDL 前,要评估锁级别和执行时间。

操作常见风险
select通常不阻塞写
update/delete锁定被修改行
alter table可能等待或阻塞业务 SQL
create index普通创建可能阻塞写,需考虑 concurrently

面试时不用死背所有锁模式,但要知道 DDL 的锁风险比普通 DML 大。

三、行级锁来自对同一行的修改

行级锁常见于 updatedeleteselect for update。如果多个事务按不同顺序修改多行,就容易出现等待甚至死锁。

事务 A: 锁住订单 1 -> 等订单 2
事务 B: 锁住订单 2 -> 等订单 1
形成死锁

数据库会检测死锁,并中止其中一个事务,让另一个继续。应用需要捕获错误并决定是否重试。

四、死锁的本质是资源顺序不一致

死锁不是 PostgreSQL 独有问题。它一般满足互斥、持有并等待、不可抢占、循环等待。工程上最常见的原因是事务访问资源顺序不一致。

解决办法是统一加锁顺序。比如批量更新账户转账时,总是先锁小 ID,再锁大 ID。

错误:A 转 B 锁 A 再锁 B;B 转 A 锁 B 再锁 A
正确:无论谁转谁,都先锁 min(account_id),再锁 max(account_id)

这个原则比单纯调参数更重要。

五、pg_stat_activity 和 pg_locks 是排查入口

锁等待排查通常从活动视图开始,看谁在等、等了多久、SQL 是什么。

select pid, state, wait_event_type, wait_event, query
from pg_stat_activity
where wait_event_type is not null;

进一步可以结合 pg_locks 查看锁对象和持有情况。很多线上排障脚本会构造阻塞链,把 blocker 和 waiter 列出来。

如果发现阻塞者是 idle in transaction,通常说明应用开启事务后没有及时提交或回滚,这是高优先级问题。

六、外键和索引也会影响锁等待

外键检查可能带来额外锁和查询。如果子表外键列没有索引,父表删除或更新时,数据库可能需要扫描子表检查引用关系,导致锁等待时间变长。

比如:

orders.user_id -> users.id
如果 orders.user_id 没索引,删除 users 某行时检查成本很高

所以有外键的列通常要建索引,尤其是子表数据量大时。即使不用数据库外键,应用层关联字段也要考虑查询和删除成本。

七、常见误区与追问

  • 误区:PostgreSQL 有 MVCC,所以不会锁等待。 MVCC 减少读写阻塞,但写写冲突、DDL 和显式锁仍会等待。
  • 误区:死锁只能靠数据库参数解决。 关键是统一资源访问顺序、缩短事务和减少锁持有时间。
  • 误区:锁等待时只看慢 SQL。 阻塞者可能已经空闲在事务中,要看阻塞链。
  • 追问:怎么查谁阻塞了谁? 结合 pg_stat_activitypg_lockspg_blocking_pids(pid)
  • 追问:DDL 为什么危险? 大表 DDL 可能申请强锁,等待期间会阻塞业务读写或被业务事务阻塞。
  • 追问:死锁后应用要做什么? 捕获死锁错误,对幂等操作做有限重试,并记录告警。

八、面试中可以这样落地

排查锁等待可以先执行:

select pid,
       pg_blocking_pids(pid) as blockers,
       wait_event_type,
       wait_event,
       query
from pg_stat_activity
where cardinality(pg_blocking_pids(pid)) > 0;

然后定位 blocker 的 SQL、事务开始时间和状态。如果是长事务,优先让业务提交、回滚或人工终止;如果是 DDL 锁等待,就调整执行窗口或使用更低锁风险的方式。

九、加强记忆

PostgreSQL 锁题记住“MVCC 不等于无锁,写写冲突会等,DDL 风险更大,死锁来自顺序不一致”。排查用 pg_stat_activitypg_lockspg_blocking_pids,治理靠短事务、统一加锁顺序、低峰 DDL 和必要索引。