postgresql源码学习(十三)—— 行锁①-行锁模式与xmax

67 篇文章 54 订阅
20 篇文章 0 订阅

一、 四种行锁 

1. 简介与兼容性分析

       pg采用元组级常规锁+xmax结合的方式实现行锁。我们曾经提到过常规锁是有很多类TAG的(typedef enum LockTagType),其中 LOCKTAG_TUPLE就是用来对元组加锁的。

       不单纯用元组级常规锁,是为了避免事务修改行过多时,锁表急剧增大导致性能劣化,并且锁表在共享内存中的大小是有限的。因此,行锁是不存储在内存中的

pg中通常有两种方式会用到行锁:

  • 对行执行update,delete操作
  • 显式指定行锁(例如select for update)

在新版本中,显式加行锁共有4种写法

行锁类型

简介

FOR UPDATE

FOR UPDATE会锁定SELECT检索到的行,就好像它们要被更新。

这可以阻止它们被其他事务锁定、修改或者删除,直到当前事务结束。

DELETE和修改主键、唯一键值的UPDATE会获得这种锁模式。

FOR NO KEY UPDATE

行为与FOR UPDATE类似,不过获得的锁较弱,不会阻塞SELECT FOR KEY SHARE。

不修改主键、唯一键值的UPDATE会获得这种锁模式。

FOR SHARE

行为与FOR NO KEY UPDATE类似,不过它在每个检索到的行上获得一个共享锁而不是排他锁。

读该行,不允许对行进行更新

FOR KEY SHARE

行为与FOR SHARE类似,不过锁更弱,不会被SELECT
FOR NO KEY UPDATE阻塞。

读该行键值,但允许对除键外的其他字段更新。在外键检查时使用该锁

4种行锁兼容性如下

4种行锁兼容性如下

详细可参考  官方文档第13章 13.3.2. Row-Level Locks

2. 对应源码

它们对应的源码在lockoptions.h文件

/*
 * 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;

       如前所述,pg行锁是由常规锁+xmax结合实现的,因此它需要建立常规锁与LockTupleMode之间的映射关系。

/*
 * Each tuple lock mode has a corresponding heavyweight lock, and one or two
 * corresponding MultiXactStatuses (one to merely lock tuples, another one to
 * update them).  This table (and the macros below) helps us determine the
 * heavyweight lock mode and MultiXactStatus values to use for any particular
 * tuple lock strength.
 *
 * Don't look at lockstatus/updstatus directly!  Use get_mxact_status_for_lock
 * instead.
 */
static const struct
{
	LOCKMODE	hwlock;  // 对应的常规锁模式
	int			lockstatus;  // 显式指定的锁模式
	int			updstatus;   // 隐式指定的锁模式
}

			tupleLockExtraInfo[MaxLockTupleMode + 1] =
{
	{							/* LockTupleKeyShare */
		AccessShareLock,          // 1级表锁
		MultiXactStatusForKeyShare,
		-1						/* KeyShare does not allow updating tuples */
	},
	{							/* LockTupleShare */
		RowShareLock,             // 2级表锁
		MultiXactStatusForShare,
		-1						/* Share does not allow updating tuples */
	},
	{							/* LockTupleNoKeyExclusive */
		ExclusiveLock,             // 7级表锁
		MultiXactStatusForNoKeyUpdate,
		MultiXactStatusNoKeyUpdate
	},
	{							/* LockTupleExclusive */
		AccessExclusiveLock,       // 8级表锁
		MultiXactStatusForUpdate,
		MultiXactStatusUpdate
	}
};

        其实这些行锁模式的兼容性是怎么来的,就是根据对应的表锁兼容性来的。例如一个delete操作,它在表上加的是3级锁,但在行上加的是8级锁。因此在表上该操作是兼容的(可以同时对一个表进行delete),但在行上是冲突的(不可以同时delete同一行)。

二、 xmax的设置

       通过前面的源码可以看到,tupleLockExtraInfo中除了保存常规锁对应的锁模式,还保存了大量MultiXact中的锁模式,MultiXact又是个什么呢?

1. MultiXactId

       通常如果只有一个事务增加行锁,那么直接将行的xmax设为事务id,并在infomask中设置对应锁类型即可。

https://i-blog.csdnimg.cn/blog_migrate/80b86120a82747a05c236fae084bb0b7.png

       但select…for…语句中可以加行级共享锁,即可以有多个事务对一个元组加共享锁,这时就没法通过将行的xmax设为事务id来表示了。为此,pg将多个事务组成一个mXactCacheEnt(multixact.c文件),并为其指定唯一的MultiXactId,此时在xmax处保存的就是MultiXactId。

typedef struct mXactCacheEnt
{
	MultiXactId multi;
	int			nmembers;
	dlist_node	node;
	MultiXactMember members[FLEXIBLE_ARRAY_MEMBER];
} mXactCacheEnt;

   为了区分xmax设置的是事务id还是MultiXactId,在使用MultiXactId时会在元组上增加HEAP_XMAX_IS_MULTI标记。

2. 模拟案例

会话1

create table t1(a int primary key,b int);
insert into t1 values(1,1);
select xmin,xmax,* from t1; -- 事务id 761

会话2

Begin;
select * from t1 for key share; -- 事务不提交

会话1

select xmin,xmax,* from t1; -- 当前xmax中保存的是事务id(762)

会话3

Begin;
select * from t1 for share; -- 事务不提交(事务id 763)

会话1

select xmin,xmax,* from t1; -- 当前xmax中保存的是MultiXactId

怎么知道这个是MultiXactId?可以通过控制文件查看

pg_controldata –D $PGDATA | grep Multi

3. 行锁标记位

  • 如果元组的xmax是事务id,需要通过infomask标记位区分元组的加锁情况。

源代码在htup_details.h,其实有很多种状态,这里只简单列一些

#define HEAP_XMAX_KEYSHR_LOCK	0x0010	/* for key share子句对应的锁 */
#define HEAP_XMAX_EXCL_LOCK		0x0040	/* xmax is exclusive locker,排他锁标记位 */
/* xmax is a shared locker */
#define HEAP_XMAX_SHR_LOCK	(HEAP_XMAX_EXCL_LOCK | HEAP_XMAX_KEYSHR_LOCK)
#define HEAP_XMAX_LOCK_ONLY		0x0080	/* xmax, if valid, is only a locker,显式加行锁 */
#define HEAP_XMAX_IS_MULTI		0x1000	/* t_xmax is a MultiXactId */
#define HEAP_KEYS_UPDATED		0x2000	/* tuple was updated and key cols */

详细可参考  https://mp.weixin.qq.com/s/nDBYbJpoBVqZxpYYaxPZ6Q

  • 如果元组的xmax是MultiXactId,则每种子句都对应一种锁模式(它们的对应关系通过tupleLockExtraInfo也可以看出来)
/*
 * Possible multixact lock modes ("status").  The first four modes are for
 * tuple locks (FOR KEY SHARE, FOR SHARE, FOR NO KEY UPDATE, FOR UPDATE); the
 * next two are used for update and delete modes.
 */
typedef enum
{
	MultiXactStatusForKeyShare = 0x00,
	MultiXactStatusForShare = 0x01,
	MultiXactStatusForNoKeyUpdate = 0x02,
	MultiXactStatusForUpdate = 0x03,
	/* an update that doesn't touch "key" columns */
	MultiXactStatusNoKeyUpdate = 0x04,
	/* other updates, and delete */
	MultiXactStatusUpdate = 0x05
} MultiXactStatus;

       为了保存MultiXactId和事务的映射关系,pg使用两个SLRU进行分层映射,它们位于$PGDATA/pg_multixact目录下,分别是offsets目录和members目录。

参考

《PostgreSQL技术内幕:事务处理深度探索》第2章

PostgreSQL锁机制——行级锁 - JavaShuo

https://www.modb.pro/db/70021

https://www.modb.pro/video/5128?sjhy

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hehuyi_In

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值