什么是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/