并发事务实现中,有时某个事务需要对上万行加锁,此时锁对象不适于放在内存中,PG采取的策略是,将加锁事务的ID设置到被锁记录的xmax中,同时设置t_infomask表示记录被锁,以此表示记录被锁住(共享锁或互斥锁)。
但是,当多个事务都要对某行记录,加共享锁时,又怎样表达呢?上述的机制,看起来没法表示多个事务对记录加共享锁的情况。
解决办法就是,用一个特殊的事务ID(MultiXactId)代替所有加锁的事务ID,然后建立MultiXactId与这些事务ID的对应关系。例如:
对于上图的场景,如果又有一个事务T4要对这行记录加锁,PG内核会怎么办呢?它会再创建一个新MultiXactId,在MutiXactOffsetCtl和MultiXactMemberCtl中建立它与新的事务集合(T1、T2、T3、T4)的映射,并用新的MultiXactId替换xmax中旧的值。而旧MultiXactId在MutiXactOffsetCtl和MultiXactMemberCtl中的数据就留在那里,不再使用,直到vacuum时删除。
当加锁的事务执行完时,并不会对记录“解锁”,记录xmax中的MultiXactId仍然保留,对于要读写或者加锁这条记录的事务而言,它必须查询映射信息(MutiXactOffsetCtl和MultiXactMemberCtl)和snapshot,判断这条记录是否仍然被某个事务加锁。
映射信息(MutiXactOffsetCtl和MultiXactMemberCtl)存储在文件中,在数据目录的pg_multixact目录下,同时在内存中有缓存,它的存储机制由SLRU模块管理,与事务日志CLOG的存储机制相同。
参考:Notes on some PostgreSQL implementation details