it编程 > 数据库 > MsSqlserver

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

77人参与 • 2026-09-28 • MsSqlserver

你的 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 种模式,强度从弱到强排下来是:

兼容矩阵我贴在下面,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

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

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

set lock_timeout = '3s';
alter table users add column age int;

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

行级锁

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

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

请求\持有for key sharefor sharefor no key updatefor update
for key sharex
for sharexx
for no key updatexxx
for updatexxxx

几个实际场景:

行锁信息存在元组头的 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,应用应该捕获这个错误并重试。

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

比如队列消费场景,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);

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

几个实践里踩过的坑

最后说一句

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

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

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

赞 (0)

您想发表意见!!点此发布评论

推荐阅读

SQL Server备份和恢复从入门到精通的完整指南

10-09

PostgreSQL PITR实现时间点误删除恢复功能

10-09

Ubuntu使用chkconfig命令时报错:chkconfig: command not found问题及解决

10-09

CentOS 7如何关闭防火墙命令

10-09

nginx在多层服务器代理接口地址的应用方式

10-09

PostgreSQL四种常见部署模式对比详解

10-10

猜你喜欢

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论