PostgreSQL 关系级锁 — 总结与优化指南

PostgreSQL 关系级锁 --- 总结与优化指南

一、核心概念速览

概念 说明
锁的本质 共享内存中的一个状态块,标识资源是否被占用
长锁 持续到事务结束,用于保护关系/行等数据库对象
短锁 持续几个 CPU 指令,用于保护内存数据结构
重锁 长锁的一种,位于共享内存,可通过 pg_locks 查看

二、重锁类型汇总

locktype 说明
virtualxid 每个事务持有自身虚拟 ID 的独占锁
transactionid 事务获取真实 XID 后额外持有的独占锁
relation 关系级锁(表、索引等)
tuple 元组级锁
object 非关系对象锁
extend 表扩展锁
page 部分索引类型使用的页级锁
advisory 用户手动管理的咨询锁

三、关系级锁的 8 种模式(由弱到强)

模式 典型命令 说明
AccessShareLock SELECT 最弱,仅与 AccessExclusive 冲突
RowShareLock SELECT FOR UPDATE/SHARE 行共享
RowExclusiveLock INSERT / UPDATE / DELETE 行独占,允许并发堆修改
ShareUpdateExclusiveLock VACUUM / CREATE INDEX CONCURRENTLY 允许并发读写,阻止 DDL
ShareLock CREATE INDEX 允许并发读,阻止写
ShareRowExclusiveLock 部分 ALTER TABLE 阻止并发写和 ShareLock
ExclusiveLock 部分 ALTER TABLE 仅允许 AccessShare 并发
AccessExclusiveLock DROP / TRUNCATE / VACUUM FULL / LOCK TABLE 最强,与所有模式冲突

关键规律:前 4 种模式允许并发堆修改;后 4 种模式不允许。


四、等待队列机制

  • 等待队列是公平的 FIFO 队列
  • 一旦队列中存在高强度锁(如 AccessExclusiveLock),后续所有请求(包括兼容的 SELECT)都必须排队等待。
  • 事务结束(提交或回滚)后,其持有的所有锁立即释放,队列中第一个进程被唤醒。

示例阻塞链

复制代码
UPDATE (RowExclusiveLock, 持有)
  └─ CREATE INDEX (ShareLock, 等待 UPDATE)
       └─ VACUUM FULL (AccessExclusiveLock, 等待 UPDATE + CREATE INDEX)
            └─ SELECT (AccessShareLock, 等待 VACUUM FULL)

五、常用监控 SQL

查看当前所有锁

sql 复制代码
SELECT pid, locktype, relation::regclass, mode, granted
FROM pg_locks
ORDER BY pid;

查看阻塞关系

sql 复制代码
SELECT pid,
       pg_blocking_pids(pid) AS blocked_by,
       wait_event_type,
       state,
       left(query, 60) AS query
FROM pg_stat_activity
WHERE cardinality(pg_blocking_pids(pid)) > 0;

查看锁等待链(依赖关系)

sql 复制代码
SELECT blocked.pid,
       blocked.query AS blocked_query,
       blocking.pid AS blocking_pid,
       blocking.query AS blocking_query
FROM pg_stat_activity AS blocked
JOIN pg_stat_activity AS blocking
  ON blocking.pid = ANY(pg_blocking_pids(blocked.pid));

六、优化建议

1. 缩短事务持锁时间

  • 避免在事务中执行耗时操作(如外部 HTTP 调用、大量计算)。

  • 尽量将非数据库操作移到事务外部。

  • 使用 idle in transaction 超时参数防止长时间持锁:

    sql 复制代码
    SET idle_in_transaction_session_timeout = '30s';

2. 优先使用非阻塞 DDL

  • CREATE INDEX CONCURRENTLY 替代 CREATE INDEX,避免长时间 ShareLock 阻塞写操作。
  • ALTER TABLE ... ADD COLUMN 时,新列默认值为 NULL 或 volatile 函数时不会重写表(PG 11+)。
  • 避免在业务高峰期执行 VACUUM FULL / TRUNCATE / DROP TABLE,这些操作需要 AccessExclusiveLock

3. 合理控制锁的数量

  • max_locks_per_transaction × max_connections 决定锁池上限,超出会报错。
  • 批量操作大量表时(如分区表),注意锁数量,必要时调大 max_locks_per_transaction(需重启)。

4. 死锁预防

  • 多个事务操作同一组资源时,保持一致的加锁顺序

  • 使用 SELECT ... FOR UPDATE 时,尽量缩小锁定范围(加 WHERE 条件)。

  • 设置合理的死锁检测超时(默认 1s):

    sql 复制代码
    SET deadlock_timeout = '500ms';
  • 死锁发生时,PostgreSQL 自动中止代价较小的事务,应用层需处理重试逻辑。

5. 使用咨询锁替代应用层分布式锁

  • 对于业务层面的互斥需求,可使用 PostgreSQL 咨询锁,避免引入外部锁服务:

    sql 复制代码
    -- 获取会话级咨询锁
    SELECT pg_advisory_lock(12345);
    -- 释放
    SELECT pg_advisory_unlock(12345);
    -- 事务级(自动释放)
    SELECT pg_advisory_xact_lock(12345);

6. 监控与告警

  • 定期检查 pg_stat_activitywait_event_type = 'Lock' 的会话。
  • idle in transaction 状态超过阈值的连接设置自动终止。
  • 结合 pg_blocking_pids() 建立锁等待告警,及时发现阻塞链。

七、参数参考

参数 默认值 说明
max_locks_per_transaction 64 每事务最大锁数(影响锁池大小,需重启)
deadlock_timeout 1s 死锁检测等待时间
lock_timeout 0(不限) 等锁超时,超时后报错
idle_in_transaction_session_timeout 0(不限) 空闲事务超时自动断开
statement_timeout 0(不限) 单条语句执行超时

八、总结

PostgreSQL 的锁机制设计精细,通过多种锁模式和公平等待队列在并发性与数据一致性之间取得平衡。实际生产中的锁问题大多源于:

  1. 长事务持锁 → 设置超时参数,优化事务边界
  2. 高强度 DDL 阻塞业务 → 使用 CONCURRENTLY 变体,选择低峰期执行
  3. 死锁 → 统一加锁顺序,应用层重试
  4. 锁池耗尽 → 调整 max_locks_per_transaction,避免单事务操作过多对象

理解锁的兼容矩阵和等待队列行为,是排查和预防 PostgreSQL 性能问题的基础。

相关推荐
2401_835956812 小时前
如何通过phpMyAdmin修改Laravel用户的密码_使用Bcrypt哈希格式更新User表字段
jvm·数据库·python
qq_342295822 小时前
如何用 error 事件全局捕获页面图片或脚本加载失败状态
jvm·数据库·python
2301_817672262 小时前
如何实现SQL视图的灰度发布_版本兼容与双重定义方案
jvm·数据库·python
Absurd5872 小时前
如何从SQL获取当前登录用户数据_使用系统上下文函数
jvm·数据库·python
吕源林2 小时前
golang如何实现消息批量消费_golang消息批量消费实现策略
jvm·数据库·python
weixin_458580122 小时前
如何解决Data Guard主库ORA-16038日志无法归档_强制日志传输报错排查
jvm·数据库·python
观测云2 小时前
观测云数据转发和存档最佳实践
数据库
djjdjdjdjjdj2 小时前
SQL如何实现动态列的分组展示_利用条件聚合实现
jvm·数据库·python
WJX_KOI2 小时前
PostgreSQL:将成为人工智能与大数据时代“赢家通吃”的数据库
数据库·postgresql