PostgreSQL 的常见锁:为什么你的 ALTER TABLE 会卡住

 更新时间:2026年09月28日 08:44:54   作者:Y..  
很多人用PG,觉得它 MVCC 很厉害,读写互不阻塞,这没错,但 MVCC 主要解决的是“读”和“写”之间的冲突,今天就把PG里常见的锁捋一遍,顺便说说怎么排查锁等待和死锁,感兴趣的朋友一起看看吧

你的 ALTER TABLE 卡住,核心原因是它需要获取 ‌ACCESS EXCLUSIVE 锁‌,这是 PostgreSQL 中最强的表级锁,会与表上所有其他锁冲突(包括普通的 SELECT)。只要有其他会话以任何方式持有了这张表的锁(哪怕只是一个长时间运行的查询),你的 ALTER TABLE 就得排队等它释放,表现就是“卡住”。

上周同事问我,为什么给一张表加个字段,整个表就像被冻住了一样,连普通的 SELECT 都进不来。其实这就是 PostgreSQL 的锁在起作用。

很多人用 PG,觉得它 MVCC 很厉害,读写互不阻塞。这没错,但 MVCC 主要解决的是“读”和“写”之间的冲突。真到了写和写、DDL 和 DML 之间,还是得靠锁来协调。今天就把 PG 里常见的锁捋一遍,顺便说说怎么排查锁等待和死锁。

先搞清楚:普通 SELECT 到底加不加锁?

这是最容易误解的地方。在 PostgreSQL 里,普通的 SELECT 只会在表上加一个 ACCESS SHARE 锁,这个锁非常弱,唯一会跟它冲突的就是 ACCESS EXCLUSIVE。也就是说,你平时查询,基本不会阻塞别人,别人也很难阻塞你。

真正会打架的是写操作和 DDL。比如 ALTER TABLE、DROP TABLE、TRUNCATE 这些通常会加 ACCESS EXCLUSIVE,这个锁一出来,所有其他访问都得排队。

所以,那个同事遇到的“加字段卡住”,大概率是 ALTER TABLE 在等 ACCESS EXCLUSIVE,而前面有长事务或者慢查询占着表,导致后面所有请求都堵住了。

表级锁

PostgreSQL 表级锁有 8 种模式,强度从弱到强排下来是:

  • ACCESS SHARE
  • ROW SHARE
  • ROW EXCLUSIVE
  • SHARE UPDATE EXCLUSIVE
  • SHARE
  • SHARE ROW EXCLUSIVE
  • EXCLUSIVE
  • ACCESS EXCLUSIVE

兼容矩阵我贴在下面,X 表示冲突,空白表示兼容:

请求\持有ACCESS SHAREROW SHAREROW EXCLUSIVESHARE UPDATE EXCLUSIVESHARESHARE ROW EXCLUSIVEEXCLUSIVEACCESS EXCLUSIVE
ACCESS SHAREX
ROW SHAREXX
ROW EXCLUSIVEXXXX
SHARE UPDATE EXCLUSIVEXXXXX
SHAREXXXXX
SHARE ROW EXCLUSIVEXXXXXX
EXCLUSIVEXXXXXXX
ACCESS EXCLUSIVEXXXXXXXX

这张表不用背,记住几个关键就行:

  • ACCESS SHARE 是最弱的,普通 SELECT 拿的就是它。它只跟 ACCESS EXCLUSIVE 冲突。
  • ROW EXCLUSIVE 是 INSERT/UPDATE/DELETE 拿的,它跟 SHARE、SHARE ROW EXCLUSIVE、EXCLUSIVE、ACCESS EXCLUSIVE 冲突。
  • ACCESS EXCLUSIVE 是老大,跟所有锁都冲突。ALTER TABLE、DROP TABLE、TRUNCATE、VACUUM FULL、CLUSTER、REINDEX 这些基本都会拿它。
  • CREATE INDEX CONCURRENTLY 比较特殊,它拿的是 SHARE UPDATE EXCLUSIVE,所以不会阻塞 DML,这也是大表建索引推荐它的原因。

我一般会提醒团队:DDL 之前一定要设 lock_timeout。不然一个 ALTER TABLE 可能因为等锁排到所有查询后面,把整个库拖慢。

SET lock_timeout = '3s';
ALTER TABLE users ADD COLUMN age int;

如果 3 秒拿不到锁,它自己就放弃了,不会一直堵着。

行级锁

行级锁是写操作在行上加的锁。普通 SELECT 不加行锁。四种行锁:

  • FOR KEY SHARE
  • FOR SHARE
  • FOR NO KEY UPDATE
  • FOR UPDATE

强度从左到右递增。兼容矩阵:

请求\持有FOR KEY SHAREFOR SHAREFOR NO KEY UPDATEFOR UPDATE
FOR KEY SHAREX
FOR SHAREXX
FOR NO KEY UPDATEXXX
FOR UPDATEXXXX

几个实际场景:

  • UPDATE 通常拿 FOR NO KEY UPDATE。如果更新的是唯一键列,可能会升级成 FOR UPDATE。
  • DELETE 通常拿 FOR UPDATE。
  • SELECT ... FOR UPDATE 显式拿最强的行锁,用来做悲观锁。
  • SELECT ... FOR SHARE 拿共享锁,别人可以读,但不能改。
  • SELECT ... FOR KEY SHARE 最弱,外键检查时经常用。比如子表插入一条记录,会去父表对应行上加 FOR KEY SHARE,防止父表键被删掉或改掉。

行锁信息存在元组头的 xmax 里。多个事务锁同一行时,PG 会用 MultiXact 来记录。行锁在事务结束时释放,所以长事务会一直占着行锁,这是很多锁等待的根源。

一个常见坑:外键会让子表写入阻塞父表键更新。比如你更新父表的主键,子表可能正在插入,两边就会互相等。设计外键时心里要有数。

锁等待和死锁,怎么查?

PG 提供了几个视图,最常用的是 pg_locks 和 pg_stat_activity。

查当前没拿到的锁:

SELECT pid, locktype, relation::regclass, mode, granted, query
FROM pg_locks l
LEFT JOIN pg_stat_activity a USING (pid)
WHERE NOT granted;

查谁被谁阻塞了:

SELECT pid, pg_blocking_pids(pid) AS blocking_pids, query
FROM pg_stat_activity
WHERE cardinality(pg_blocking_pids(pid)) > 0;

pg_blocking_pids 这个函数特别好用,直接告诉你哪个进程堵住了当前查询。

如果要杀查询,有两个选择:

SELECT pg_cancel_backend(pid);   -- 取消当前查询,连接还在
SELECT pg_terminate_backend(pid); -- 直接断开连接

一般先 cancel,不行再 terminate。

死锁是另一个话题。PG 有 deadlock_timeout,默认 1 秒。如果一个事务等锁超过这个时间,就会触发死锁检测。检测到循环等待,它会杀掉其中一个事务,报 deadlock detected,应用应该捕获这个错误并重试。

死锁没法完全避免,但可以降低概率:

  • 事务尽量短,别在事务里等用户输入。
  • 多个表更新时,固定访问顺序。
  • 用 SELECT ... FOR UPDATE NOWAIT 或 SKIP LOCKED 避免无限等待。

比如队列消费场景,SKIP LOCKED 就很好用:

SELECT * FROM jobs
WHERE status = 'pending'
ORDER BY id
LIMIT 1
FOR UPDATE SKIP LOCKED;

这样多个 worker 可以同时取任务,不会互相等。

咨询锁:应用层的互斥

除了表锁和行锁,PG 还有咨询锁(Advisory Lock)。它不绑定任何数据库对象,纯粹是应用自己约定的一把锁。

会话级:

SELECT pg_advisory_lock(12345);
SELECT pg_advisory_unlock(12345);

事务级:

SELECT pg_advisory_xact_lock(12345);

事务级会在事务结束时自动释放,用起来更省心。咨询锁适合做“同一时间只有一个任务跑”这种场景,比如定时任务、分布式锁的简易实现。但注意,它只在同一个数据库集群内有效,跨实例不行。

几个实践里踩过的坑

  • 长事务是万恶之源。一个事务开着不提交,行锁不释放,VACUUM 也清不掉死元组。监控 pg_stat_activity 里 state = 'idle in transaction' 的会话。
  • DDL 一定要加 lock_timeout。尤其是 ALTER TABLE,不加锁超时,它可能排在一个长查询后面,把后面所有请求都堵死。
  • 大表建索引用 CREATE INDEX CONCURRENTLY。普通 CREATE INDEX 会拿 SHARE 锁,阻塞写。并发建索引虽然慢一点,但不阻塞 DML。
  • VACUUM FULL 和 CLUSTER 会拿 ACCESS EXCLUSIVE,生产环境慎用,最好在低峰期做。
  • 隔离级别要心里有数。READ COMMITTED 是默认,每个语句一个快照。REPEATABLE READ 和 SERIALIZABLE 可能报序列化失败,应用要有重试逻辑。
  • 监控锁等待。可以定期查 pg_locks 里 granted = false 的记录,或者用 pg_blocking_pids 看阻塞链。

最后说一句

PostgreSQL 的锁并不复杂,复杂的是不知道谁在等谁。MVCC 让读不阻塞写,但写写冲突、DDL 冲突还是得靠锁。遇到卡顿,先看 pg_locks 和 pg_stat_activity,找到阻塞源头,再决定是等、是取消,还是优化事务。

锁不是敌人,它只是并发控制的工具。理解它,你就能少踩很多坑。

到此这篇关于PostgreSQL 的锁:为什么你的 ALTER TABLE 会卡住,以及怎么查的文章就介绍到这了,更多相关PostgreSQL  ALTER TABLE卡住内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 查看postgresql数据库用户系统权限、对象权限的方法

    查看postgresql数据库用户系统权限、对象权限的方法

    这篇文章主要介绍了查看postgresql数据库用户系统权限、对象权限的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-12-12
  • CentOS 9 Stream 上安装 PostgreSQL 16的步骤

    CentOS 9 Stream 上安装 PostgreSQL 16的步

    在CentOS9Stream上安装PostgreSQL16,首先添加PostgreSQL官方仓库,然后禁用系统自带PostgreSQL版本,避免冲突,使用dnf命令安装PostgreSQL16,并初始化数据库,本文给大家介绍CentOS 9 Stream 上安装 PostgreSQL 16的步骤,感兴趣的朋友一起看看吧
    2024-11-11
  • postgresql数据库表ID自增的实现代码

    postgresql数据库表ID自增的实现代码

    postgresql数据库可以创建主键,但是没有像mysql那样直接指定主键自增的auto_increment关键字,因此如果在postgresql中创建表指定主键自增使用auto_increment会报错,本文通过一个实例给大家演示自增ID的实现,需要的朋友可以参考下
    2023-12-12
  • PostgreSQL完成按月累加的操作

    PostgreSQL完成按月累加的操作

    这篇文章主要介绍了PostgreSQL完成按月累加的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-01-01
  • Postgresql 截取字符串的案例

    Postgresql 截取字符串的案例

    这篇文章主要介绍了Postgresql 截取字符串的案例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-02-02
  • PostgreSQL AUTO INCREMENT(自动增长) 的使用

    PostgreSQL AUTO INCREMENT(自动增长) 的使用

    本文主要介绍 PostgreSQL的自动增长机制,包括 SERIAL(传统方式)、IDENTITY(推荐标准)和 SEQUENCE(底层对象)三种实现,具有一定的参考价值,感兴趣的可以了解一下
    2025-11-11
  • PostgreSQL序列用法小结

    PostgreSQL序列用法小结

    PostgreSQL中序列Sequence是一个独立的数据库对象,专门用于生成唯一的递增整数,最常用于为表字段生成自增主键,下面详细解析序列的用法、核心函数以及实战中的避坑指南,感兴趣的可以了解一下
    2026-06-06
  • PostgreSQL定时清理旧数据的实现方法

    PostgreSQL定时清理旧数据的实现方法

    最近觉得数据库中每日数据不需要都保持,只需要保留30天的,所以这篇文章给大家介绍了PostgreSQL定时清理旧数据的实现方法,文中通过代码示例和图文给大家介绍的非常详细,具有一定的参考价值,需要的朋友可以参考下
    2024-03-03
  • PostgreSQL优雅的进行递归查询的实战指南

    PostgreSQL优雅的进行递归查询的实战指南

    在实际开发中,我们经常会遇到树形结构或图结构的数据需求,这些场景的核心问题是:如何高效查询具有层级/递归关系的数据?所以本文将从基础到实战,手把手教你掌握递归查询的精髓,需要的朋友可以参考下
    2026-01-01
  • postgreSQL 中的自定义操作符示例详解

    postgreSQL 中的自定义操作符示例详解

    文章介绍了PostgreSQL在操作符定义方面的创新和灵活性,包括如何使用OPERATOR()语法显式调用操作符,以及如何自定义操作符以满足特定需求,文章还讨论了PostgreSQL在自定义操作符方面的优势,如操作符重载、索引绑定和对优化器的影响,感兴趣的朋友跟随小编一起看看吧
    2025-12-12

最新评论