pg事务:multixact

什么是multixact?

在对同一行加锁时,元组上关联的事务ID可能有多个,pg将多个事务ID组合起来用一个MultiXactID来管理。TransactionId和MultiXactID是多对一的关系

在这里插入图片描述

multixactID跟TransactionId一样,也是32位,同样有wraparound

MultiXactId的0、1都是系统使用,可分配的MultiXactId从2开始

源码src/include/access/multixact.h
#define InvalidMultiXactId	((MultiXactId) 0)
#define FirstMultiXactId	((MultiXactId) 1)
#define MaxMultiXactId		((MultiXactId) 0xFFFFFFFF)

行锁的类型

只有行上有锁时,才会有multixact。MultiXact总共定义了6种状态

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;

其中能显示声明的行锁的状态有4种:ForKeyShare,ForShare,ForNoKeyUpdate,ForUpdate

multixact的infomask标记

pg会将行锁标记到xmax上并记录到infomask中
源码src/include/access/htup_details.h

#define HEAP_XMAX_KEYSHR_LOCK	0x0010	/* xmax is a key-shared locker */
#define HEAP_XMAX_EXCL_LOCK		0x0040	/* xmax is exclusive locker */
#define HEAP_XMAX_LOCK_ONLY		0x0080	/* xmax, if valid, is only a locker */
#define HEAP_XMAX_SHR_LOCK	(HEAP_XMAX_EXCL_LOCK | HEAP_XMAX_KEYSHR_LOCK)
#define HEAP_LOCK_MASK	(HEAP_XMAX_SHR_LOCK | HEAP_XMAX_EXCL_LOCK | \
						 HEAP_XMAX_KEYSHR_LOCK)
#define HEAP_XMAX_IS_MULTI		0x1000	/* t_xmax is a MultiXactId */

这里重点模拟HEAP_XMAX_IS_MULTI标记,只有多个事务对同一行持有共享锁时才真正产生multixact id,才会有此标记

lzldb=# insert into lzl1 values(1);  --初始只有1行数据
INSERT 0 1
lzldb=# select * from vlzl1;
 t_ctid | lp | lp_flags  | t_xmin | t_xmax | t_cid |            raw_flags             | combined_flags 
--------+----+-----------+--------+--------+-------+----------------------------------+----------------
 (0,1)  |  1 | LP_NORMAL |    742 |      0 |     0 | {HEAP_HASNULL,HEAP_XMAX_INVALID} | {}
(1 row)
窗口1 窗口2
lzldb=# begin;
BEGIN
lzldb=*# select * from lzl1 for share;
a

1
lzldb=# begin;
BEGIN
lzldb=*# select * from lzl1 for share;
a

1
lzldb=*# update lzl1 set a=2; --hang
commit;
UPDATE 1 --update更新完成
--查看元组xmax、infomask情况
lzldb=*#  select t_ctid,lp,t_xmin,t_xmax,(t_infomask&4096)!=0 is_multixact  from heap_page_items(get_raw_page('lzl1',0));
 t_ctid | lp | t_xmin | t_xmax | is_multixact 
--------+----+--------+--------+--------------
 (0,2)  |  1 |    742 |      4 | t
 (0,2)  |  2 |    744 |      3 | t

HEAP_XMAX_IS_MULTI的16进制值是1000,转换为10进制为4096,通过(t_infomask&4096)!=0 is_multixact可以看出元组是否使用了multixact id。从上面的例子可以看出:

  • multxact id不同于transaction id,它有自己的取值空间
  • multixact id一般来说比transaction id小,所以这里t_xmax比t_xmin更小
  • 如果是一个update语句更新的元组,那么新旧元组的xmax肯定是相等的。但是在multixact的场景下,可能就不一样了。

multixact slru

虽然src/backend/access/transam/multixact.c的开头定义很多变量和函数,有page,member,membergoup,offset,但是总体都是定义变量值,然后定义这些变量进行相互转化的函数

读懂multixact.c前需要先了解几个宏定义

src/include/c.h中定义MultiXactOffset是32位的类型

typedef uint32 MultiXactOffset;

src/include/access/slru.h中定义每个段有多少SLRU PAGES

#define SLRU_PAGES_PER_SEGMENT	32

回到src/backend/access/transam/multixact.c的开头

define MULTIXACT_OFFSETS_PER_PAGE (BLCKSZ / sizeof(MultiXactOffset))  
//MULTIXACT_OFFSETS_PER_PAGE=8k/32B=2048 一个page可以存储2048个offsets标识位,其实也是2048个MULTIXACTID

#define MultiXactIdToOffsetPage(xid) \
	((xid) / (MultiXactOffset) MULTIXACT_OFFSETS_PER_PAGE)
//通过xid转换为对应记录的位于的页面:xid/2048

#define MultiXactIdToOffsetEntry(xid) \
	((xid) % (MultiXactOffset) MULTIXACT_OFFSETS_PER_PAGE)
//通过xid转换为对应记录位于页面的偏移量:xid%2048

#define MultiXactIdToOffsetSegment(xid) (MultiXactIdToOffsetPage(xid) / SLRU_PAGES_PER_SEGMENT)
//通过xid转换为对应记录位于的segment:xid/2048/32

再看下源码开头的注释

/* 
 * Defines for MultiXactOffset page sizes.  A page is the same BLCKSZ as is
 * used everywhere else in Postgres.
 *
 * Note: because MultiXactOffsets are 32 bits and wrap around at 0xFFFFFFFF,
 * MultiXact page numbering also wraps around at
 * 0xFFFFFFFF/MULTIXACT_OFFSETS_PER_PAGE, and segment numbering at
 * 0xFFFFFFFF/MULTIXACT_OFFSETS_PER_PAGE/SLRU_PAGES_PER_SEGMENT.  We need
 * take no explicit notice of that fact in this module, except when comparing
 * segment and page numbers in TruncateMultiXact (see
 * MultiXactOffsetPagePrecedes).
 */

因为MultiXactOffsets是32位且有wraparound,所以

MultiXact页编号回卷于0xFFFFFFFF/MULTIXACT_OFFSETS_PER_PAGE=232/2048=2^21

段编号回卷于0xFFFFFFFF/MULTIXACT_OFFSETS_PER_PAGE/SLRU_PAGES_PER_SEGMENT=232/211/25=2^16

TruncateMultiXact()会清理这些段包括页编号,TruncateMultiXact()被vacuum调用

pg_multixact目录

同CLOG,SUBTRANS日志一样,multixact日志SLRU缓冲池实现。pg_multixact目录下只有两个目录member,offset

[pg@lzl pg_multixact]$ ll
total 8
drwx------ 2 pg pg 4096 Feb 14 21:29 members
drwx------ 2 pg pg 4096 Feb 14 21:29 offsets

一个mutixactid有对应多个transaction id,也就是member。offset是每个multiact的起始位置

在这里插入图片描述

typedef struct mXactCacheEnt
{
    
    
	MultiXactId multi; //一个MultiXactId
	int		nmembers;
	dlist_node	node;
	MultiXactMember members[FLEXIBLE_ARRAY_MEMBER]; //多个TransactionId,如果需要,pg用MultiXactIdExpand()扩展member
} mXactCacheEnt;

multixact.h中定义了MultiXactMember只是单个事务id和事务状态

typedef struct MultiXactMember
{
    
    
	TransactionId xid;
	MultiXactStatus status;
} MultiXactMember;

multixact参考

https://www.postgresql.org/docs/current/routine-vacuuming.html

https://pgpedia.info/m/multixact-id.html

https://www.postgresql.org/docs/15/explicit-locking.html

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

https://www.highgo.ca/2020/06/12/transactions-in-postgresql-and-their-mechanism/

猜你喜欢

转载自blog.csdn.net/qq_40687433/article/details/130783329