数据库的路上

从 PostgreSQL 源码看锁模式:8 大核心锁的深度解析

在 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;

源码追踪

源码查看

image-20250614211343770

兼容性

兼容除了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;

image-20250616204724877

获取快速路径锁

通过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 */

image-20250704201617307

已经获取到的锁
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行的锁。

image-20250706162133930

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

计算new_infomask image-20250706171850248

image-20250706162316223

  • 写入wal日志

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

image-20250706180951411

事务B执行,进入等待状态
update customers set name='test' where customer_id=3;

执行update过程中,通过result = HeapTupleSatisfiesUpdate(&oldtup, cid, buffer) 检测到当前需要更新的行被其他事务正在修改,所以需要等待其他事务释放锁资源。

image-20250709221342579

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

image-20250710210340006

兼容性

兼容除了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

image-20250712220240448

兼容性

兼容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

image-20250713114555615

兼容性

兼容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锁。

image-20250713210215410

事务B:插入新数据
insert into t1 values(10,'test');

insert(update,delete同样) 会在表上申请加RowExclusiveLock,这个锁和事务A的ShareLock锁冲突,所以需要等待。

image-20250713211733563

image-20250713212302192

6. ShareRowExclusiveLock(6 号锁:共享行排他锁)

核心特性:表级排他,行级共享

特殊场景:类似EXCLUSIVE MODE,但允许行共享锁

源码注释

该锁用于特殊协同场景,源码中注释说明其「allows ROW SHARE but blocks higher modes」,实际使用较少,调试时可关注LockRelation函数中该锁的兼容性判断逻辑。

image-20250713215920291

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

image-20250714203908749

事务B:允许查询数据

select * from orders;

image-20250714204201604

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;

image-20250714210346957

事务B:

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

image-20250714212843721

总结:掌握锁模式编号就是掌握内核语言

PostgreSQL 的 8 个锁模式宏定义,本质上是一套从 SQL 行为到内核控制的「翻译系统」:

  • 1-3 号控制行级访问(读 / 写 / 独占)

  • 4-6 号协调表级与行级的锁意图

  • 7-8 号实现对表结构和全局资源的强控制

理解这些数字的含义,就能在遇到锁等待时快速定位:比如看到mode=8就知道有 DDL 操作阻塞,看到mode=3就联想到行级更新冲突。下次调试数据库阻塞问题时,不妨从pg_locks视图的mode字段入手,结合源码宏定义,让锁模式的「数字密码」成为解锁性能瓶颈的关键钥匙。