一、VM概述
He3DB for PG中为了实现多版本并发控制,当事务删除或更新元组时,并非从物理上删除,而是将其标记为无效的方式进行标记删除,最终对这些无效元组的清理操作需要调用VACUUM完成。
为了能够加快VACUUM查找包含无效元组的文件块的过程,在PostgreSQL8.4.1中为每个表文件定义了一个新的附属文件–可见性映射表(VM)。VM中为表的每个文件块设置了一位,用来标记该文件块是否存在无效元组。对包含无效元组的文件块,VACUUM有两种方式处理,即快速清理(Lazy VACUUM)和完全清理(Full VACUUM)。VM文件仅在Lazy VACUUM操作中被使用到,而Full VACUUM操作由于要执行跨块清理等复杂操作,需要对整个表文件进行扫描,这时候VM文件的作用并不大。当前,VM文件仅仅是作为一个提示(hint)来加快VACUUM的速度,所以即使VM文件损坏也仅仅会导致VACUUM忽略那些需要清理的页面,而不会对数据产生任何负面影响。
与其他文件一样,VM文件也被划分为若干个文件块(简称VM块)。VM块中除了必要的标记信息外,其他的每一位都对应一个表块,当表块中所有的元组对当前的事务都是可见的时候,表块对应的位才设置为1。其文件块结构如下图所示。
PageHeaderData | bit | bit | bit | bit … |
---|
每个VM文件块中能够记录size = (blcksz - SizeOfPageHeaderData) * 8个表块的信息,第一个VM块记录第1至size号表块的信息,第二个VM块记录第size + 1至2 * size + 1号表块的信息,依此类推。
当对某个表块中的元组进行更新或者删除后,那么该表块在VM文件中对应位置的标志位将被置0,表示有无效元组。在设置标志位的时候,需要对其对应的VM页面加锁。这是为了避免在VACUUM判断该页面是否对所有事务可见的同时,其他进程修改该页面,从而导致VACUUM清理过程中忽略了此页面。
当标志位为1时,表示没有无效元组,VACUUM操作会忽略扫描对应的表块,所以能大大提高VACUUM的效率。由于VM文件不跟踪索引,所以对索引的清理操作还是需要进行完全扫描。
二、VM源码解析
1、visibilitymap_set
功能:
- 设置可见性标志位
void
visibilitymap_set(Relation rel, BlockNumber heapBlk, Buffer heapBuf,
XLogRecPtr recptr, Buffer vmBuf, TransactionId cutoff_xid,
uint8 flags)
{
#ifdef HEAP_DEBUG1
elog(DEBUG1,"visibilitymap_set heapBlk=%u,heapBuf=%d,recptr=%X/%X,vmBuf=%d,cutoff_xid=%u,flags=%u",heapBlk,heapBuf,(uint32) ((recptr) >> 32), ((uint32) (recptr)),vmBuf,cutoff_xid,flags);
#endif
/* 根据堆块号,计算对应的可见性映射块号、字节偏移和位偏移 */
BlockNumber mapBlock = HEAPBLK_TO_MAPBLOCK(heapBlk);
uint32 mapByte = HEAPBLK_TO_MAPBYTE(heapBlk);
uint8 mapOffset = HEAPBLK_TO_OFFSET(heapBlk);
Page page;
uint8 *map;
#ifdef TRACE_VISIBILITYMAP
elog(DEBUG1, "vm_set %s %d", RelationGetRelationName(rel), heapBlk); // 输出调试信息,帮助开发者跟踪函数的执行
#endif
Assert(InRecovery || XLogRecPtrIsInvalid(recptr));
Assert(InRecovery || BufferIsValid(heapBuf));
Assert(flags & VISIBILITYMAP_VALID_BITS);
/* 确保传入的堆缓冲区和可见性映射缓冲区与预期的块号相匹配 */
if (BufferIsValid(heapBuf) && BufferGetBlo