在 PostgreSQL 源码中,锁模式通过src/include/storage/lockdefs.h中的 8 个宏定义实现,构成了关系型数据库并发控制的「数字密码本」。这些宏定义不仅明确了锁的权限等级,更直接决定了 SQL 语句的并发行为。本文将对照源码定义,带您逐行解析每个锁模式的「数字身份」,结合具体 SQL 场景和调试技巧,揭秘数据库内核的锁控制逻辑。
一、源码中的锁模式「数字身份证」
标准锁模式
PostgreSQL 通过整数枚举定义 8 种核心锁模式,每个数字对应唯一的并发控制规则:
#define AccessShareLock 1 /* SELECT */
#define RowShareLock 2 /* SELECT FOR UPDATE/FOR SHARE */
#define RowExclusiveLock 3 /* INSERT, UPDATE, DELETE */
#define ShareUpdateExclusiveLock 4 /* VACUUM (non-FULL), ANALYZE, CREATE INDEX CONCURRENTLY */
#define ShareLock 5 /* CREATE INDEX (WITHOUT CONCURRENTLY) */
#define ShareRowExclusiveLock 6 /* like EXCLUSIVE MODE, but allows ROW SHARE */
#define ExclusiveLock 7 /* blocks ROW SHARE/SELECT...FOR UPDATE */
#define AccessExclusiveLock 8 /* ALTER TABLE, DROP TABLE, VACUUM FULL, and unqualified LOCK TABLE */
行锁模式
行锁模式最终实现时需要转换成标准锁模式
/*
* Possible lock modes for a tuple.
*/
typedef enum LockTupleMode
{
/* SELECT FOR KEY SHARE */
LockTupleKeyShare,
/* SELECT FOR SHARE */
LockTupleShare,
/* SELECT FOR NO KEY UPDATE, and UPDATEs that don't modify key columns */
LockTupleNoKeyExclusive,
/* SELECT FOR UPDATE, UPDATEs that modify key columns, and DELETE */
LockTupleExclusive,
} LockTupleMode;
| 行锁模式 | 对应的标准锁模式 |
|---|---|
| LockTupleKeyShare | AccessShareLock |
| LockTupleShare | RowShareLock |
| LockTupleNoKeyExclusive | ExclusiveLock |
| LockTupleExclusive | AccessExclusiveLock |
可以加锁的对象类型
* The LockTagType enum defines the different kinds of objects we can lock.
* We can handle up to 256 different LockTagTypes.
*/
typedef enum LockTagType
{
LOCKTAG_RELATION, /* whole relation */
LOCKTAG_RELATION_EXTEND, /* the right to extend a relation */
LOCKTAG_DATABASE_FROZEN_IDS, /* pg_database.datfrozenxid */
LOCKTAG_PAGE, /* one page of a relation */
LOCKTAG_TUPLE, /* one physical tuple */
LOCKTAG_TRANSACTION, /* transaction (for waiting for xact done) */
LOCKTAG_VIRTUALTRANSACTION, /* virtual transaction (ditto) */
LOCKTAG_SPECULATIVE_TOKEN, /* speculative insertion Xid and token */
LOCKTAG_OBJECT, /* non-relation database object */
LOCKTAG_USERLOCK, /* reserved for old contrib/userlock code */
LOCKTAG_ADVISORY, /* advisory user locks */
LOCKTAG_APPLY_TRANSACTION, /* transaction being applied on a logical
* replication subscriber */
} LockTagType;
二、锁模式兼容性矩阵
结合源码枚举顺序,兼容性矩阵以数字编号为索引,✅表示兼容,❌表示冲突:
| 已有锁 \ 申请锁 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|---|---|---|---|---|---|---|---|---|
| 1 (AccessShare) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
| 2 (RowShare) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| 3 (RowExclusive) | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
| 4 (ShareUpdateExclusive) | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| 5 (ShareLock) | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
| 6 (ShareRowExclusive) | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| 7 (ExclusiveLock) | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| 8 (AccessExclusive) | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
三、逐行解析 8 大锁模式:从数字到行为的映射
1. AccessShareLock(1 号锁:最弱读锁)
当执行 SELECT 查询时,PostgreSQL 会自动为目标表添加 ACCESS SHARE LOCK,防止其他事务在查询期间执行会修改表结构的操作(如 DROP TABLE)。
核心特性:仅允许并发读,兼容所有非排他锁
触发场景:普通SELECT语句隐式获取
示例 SQL:
select * from customers where customer_id=2;
源码追踪:
源码查看

兼容性
兼容除了AccessExclusiveLock外所有类型的锁。
2. RowShareLock(2 号锁:行级共享锁)
核心特性:允许其他事务加行共享锁,禁止行排他锁
触发场景:SELECT FOR SHARE,FOR UPDATE显式申请,都会在表上加rowsharelock。他们的区别是加到行的锁模式锁不同的。
示例 SQL:
-- 事务A获取2号锁(RowShareLock)
BEGIN;
select * from customers where customer_id=3 for share;
-- 事务B执行SELECT FOR SHARE可成功,但UPDATE会阻塞
源码追踪:
事务A执行sql
BEGIN;
select * from customers where customer_id=3 for share;
确定锁模式
在pg_analyze_and_rewrite_fixedparams(分析语义,制定执行计划之前)阶段,确定锁模式
确定锁的模式,for share 是RowShareLock
lockmode = isLockedRefname(pstate, refname) ? RowShareLock : AccessShareLock;

获取快速路径锁
通过FastPathGrantRelationLock 获取快速路径锁
在postgresql 中,快速路径锁(Fast Path Locks)是一种轻量级锁机制,用于在特定场景下提高并发性能
#define EligibleForRelationFastPath(locktag, mode) \
((locktag)->locktag_lockmethodid == DEFAULT_LOCKMETHOD && \
(locktag)->locktag_type == LOCKTAG_RELATION && \
(locktag)->locktag_field1 == MyDatabaseId && \
MyDatabaseId != InvalidOid && \
(mode) < ShareUpdateExclusiveLock)
这三种锁可以申请到快速路径锁
#define AccessShareLock 1 /* SELECT */
#define RowShareLock 2 /* SELECT FOR UPDATE/FOR SHARE */
#define RowExclusiveLock 3 /* INSERT, UPDATE, DELETE */

已经获取到的锁
demo_db=# select locktype,relation::regclass,mode from pg_locks where pid='122733';
locktype | relation | mode
------------+-----------+---------------
relation | customers | RowShareLock
添加行锁
select … for share 的数据是不能被其他事务修改的,postgresql 是通过在对应tuple中加入了锁标记。
- heap_lock_tuple函数添加对应tuple行的锁。

- 具体原理是在tuple 头信息处设置t_infomask,添加锁标记。
计算new_infomask


-
写入wal日志
由于行锁修改了数据头部信息,需要写入日志。

事务B执行,进入等待状态
update customers set name='test' where customer_id=3;
执行update过程中,通过result = HeapTupleSatisfiesUpdate(&oldtup, cid, buffer) 检测到当前需要更新的行被其他事务正在修改,所以需要等待其他事务释放锁资源。

1290699 ? ts 0:00 \_ postgres: postgres demo_db [local] UPDATE waiting

兼容性
兼容除了ExclusiveLock,AccessExclusiveLock 其他所有类型的锁
3. RowExclusiveLock(3 号锁:行级排他锁)
核心特性:独占行记录,阻塞所有行级锁
触发场景:INSERT/UPDATE/DELETE隐式获取
示例 SQL:
-- 事务A执行更新,获取3号锁(RowExclusiveLock)
BEGIN;
delete from customers where id=3;
-- 事务B对同一行的任何操作(包括SELECT FOR SHARE)都会阻塞
源码追踪
INSERT, UPDATE, DELETE 操作都会使用setTargetTable 打开目标表并加锁,这一步加的就是RowExclusiveLock

兼容性
兼容AccessShareLock,RowShareLock,RowExclusiveLock,ShareUpdateExclusiveLock。
4. ShareUpdateExclusiveLock(4 号锁:共享更新排他锁)
核心特性:禁止并发 DDL,允许数据读写
触发场景:VACUUM(非全量)、ANALYZE、并发创建索引
示例 SQL:
-- 执行非阻塞索引创建,获取4号锁
CREATE INDEX CONCURRENTLY on products (name);
-- 此时允许SELECT/INSERT,但禁止ALTER TABLE等DDL操作
源码追踪:
事务A:并发创建索引
CREATE INDEX CONCURRENTLY on products (name);
并发创建索引时,获取的锁就是ShareUpdateExclusiveLock,不并发时获取的锁级别更高是ShareLock

兼容性
兼容AccessShareLock,RowShareLock,RowExclusiveLock
5. ShareLock(5 号锁:表级共享锁)
核心特性:允许并发读,禁止表级写
触发场景:非并发创建索引(CREATE INDEX)
示例 SQL:
-- 创建非并发索引,获取5号锁
BEGIN;
create index ind1 on t1(id);
-- 此时其他事务可SELECT,但INSERT/UPDATE会阻塞
源码追踪
事务A: 普通创建索
BEGIN;
create index ind1 on t1(id);
普通创建索引在表上加了ShareLock锁。

事务B:插入新数据
insert into t1 values(10,'test');
insert(update,delete同样) 会在表上申请加RowExclusiveLock,这个锁和事务A的ShareLock锁冲突,所以需要等待。


6. ShareRowExclusiveLock(6 号锁:共享行排他锁)
核心特性:表级排他,行级共享
特殊场景:类似EXCLUSIVE MODE,但允许行共享锁
源码注释:
该锁用于特殊协同场景,源码中注释说明其「allows ROW SHARE but blocks higher modes」,实际使用较少,调试时可关注LockRelation函数中该锁的兼容性判断逻辑。

7. ExclusiveLock(7 号锁:强排他锁)
核心特性:阻塞所有行级共享锁
触发场景:显式执行LOCK TABLE IN EXCLUSIVE MODE
示例 SQL:
-- 显式获取7号锁
BEGIN;
LOCK TABLE orders IN EXCLUSIVE MODE;
-- 事务B执行SELECT FOR SHARE会阻塞(因7号锁禁止ROW SHARE)
兼容性:
该锁级别很高,仅和1号锁AccessShareLock兼容,在表示有该锁时,其他事务仅可查询数据,无法有其他操作。
源码追踪
事务A:LOCK TABLE IN EXCLUSIVE MODE

事务B:允许查询数据
select * from orders;

8. AccessExclusiveLock(8 号锁:完全独占锁)
核心特性:最强锁模式,禁止任何并发访问
触发场景:DDL 操作(ALTER TABLE/DROP TABLE)、全量 VACUUM
示例 SQL:
-- 执行表结构修改,自动获取8号锁
BEGIN;
ALTER TABLE products ADD COLUMN new_field TEXT;
-- 此时所有读写事务都会阻塞,直到提交
兼容性
该锁对对象资源实现独占,不和任何锁模式兼容
源码追踪
事务A:
ALTER TABLE products ADD COLUMN new_field TEXT;

事务B:
拒绝任何对该对象的操作。即使是模式1的轻量锁,也会产生锁等待。

总结:掌握锁模式编号就是掌握内核语言
PostgreSQL 的 8 个锁模式宏定义,本质上是一套从 SQL 行为到内核控制的「翻译系统」:
-
1-3 号控制行级访问(读 / 写 / 独占)
-
4-6 号协调表级与行级的锁意图
-
7-8 号实现对表结构和全局资源的强控制
理解这些数字的含义,就能在遇到锁等待时快速定位:比如看到mode=8就知道有 DDL 操作阻塞,看到mode=3就联想到行级更新冲突。下次调试数据库阻塞问题时,不妨从pg_locks视图的mode字段入手,结合源码宏定义,让锁模式的「数字密码」成为解锁性能瓶颈的关键钥匙。