pg_visibility是一个PostgreSQL数据库插件,提供了一种方式来检查一个表的可见性映射(VM)以及页级别的可见性信息。
插件如何使用?
该插件提供了一些函数,通过调用这些函数,可以获取给定关系的可见性信息。用法示例如下:
-- 安装插件
postgres=# create extension pg_visibility;
CREATE EXTENSION
-- 为给定关系的给定块返回其在可见性映射中的“全部可见”和“全部冻结”位。
postgres=# select * from pg_visibility_map('t3',0);
all_visible | all_frozen
-------------+------------
t | f
(1 row)
-- 为给定关系的给定块返回其在可见性映射中的“全部可见”和“全部冻结”位,外加块的PD_ALL_VISIBLE位。
postgres=# select * from pg_visibility('t3',0);
all_visible | all_frozen | pd_all_visible
-------------+------------+----------------
t | f | t
(1 row)
-- 为给定关系的每一块返回其在可见性映射中的“全部可见”和“全部冻结”位,外加每一块的PD_ALL_VISIBLE位。
postgres=# select * from pg_visibility('t2');
blkno | all_visible | all_frozen | pd_all_visible
-------+-------------+------------+----------------
0 | t | t | t
1 | t | t | t
2 | t | t | t
3 | t | t | t
4 | t | t | t
5 | t | t | t
6 | t | t | t
7 | t | t | t
8 | t | f | t
(9 rows)
更多用法可参考pg_visibility
其核心原理就是读取可见性文件以及页面数据,通过位运算来获取页面的可见性信息。页头部有个pd_flags字段,其中包含PD_ALL_VISIBLE位,如果该位为1,则表示该页面所有元组可见。
// 判断页面是否全部可见
#define PageIsAllVisible(page) \
(((PageHeader) (page))->pd_flags & PD_ALL_VISIBLE)
#define PD_HAS_FREE_LINES 0x0001 /* are there any unused line pointers? */
#define PD_PAGE_FULL 0x0002 /* not enough free space for new tuple? */
#define PD_ALL_VISIBLE 0x0004 /* all tuples on page are visible to everyone */
#define PD_VALID_FLAG_BITS 0x0007 /* OR of all valid pd_flags bits */
函数调用栈:
pg_visibility.so!collect_visibility_data(Oid relid, _Bool include_pd) (pg_visibility\pg_visibility.c:505)
pg_visibility.so!pg_visibility_rel(FunctionCallInfo fcinfo) (pg_visibility\pg_visibility.c:225)
核心函数实现,获取当前表有多少个页,然后查找VM块文件,读取可见性信息,如果需要获取页可见性信息,则读取页面数据,获取pd_flags字段,判断是否全部可见。
static vbits *collect_visibility_data(Oid relid, bool include_pd)
{
Relation rel;
BlockNumber nblocks;
vbits *info;
BlockNumber blkno;
Buffer vmbuffer = InvalidBuffer;
BufferAccessStrategy bstrategy = GetAccessStrategy(BAS_BULKREAD);
rel = relation_open(relid, AccessShareLock);
/* Only some relkinds have a visibility map */
check_relation_relkind(rel);
nblocks = RelationGetNumberOfBlocks(rel); // 获取表有多少个页
info = palloc0(offsetof(vbits, bits) + nblocks);
info->next = 0;
info->count = nblocks;
for (blkno = 0; blkno < nblocks; ++blkno) // 遍历所有页号
{
int32 mapbits;
/* Make sure we are interruptible. */
CHECK_FOR_INTERRUPTS();
// 获取指定表页号的位图信息
mapbits = (int32) visibilitymap_get_status(rel, blkno, &vmbuffer);
if ((mapbits & VISIBILITYMAP_ALL_VISIBLE) != 0)
info->bits[blkno] |= (1 << 0);
if ((mapbits & VISIBILITYMAP_ALL_FROZEN) != 0)
info->bits[blkno] |= (1 << 1);
// 是否查看页面可见性信息
if (include_pd)
{
Buffer buffer;
Page page;
// 读页到buffer
buffer = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL, bstrategy);
LockBuffer(buffer, BUFFER_LOCK_SHARE);
page = BufferGetPage(buffer);
if (PageIsAllVisible(page)) // 判断页面是否全部可见
info->bits[blkno] |= (1 << 2);
UnlockReleaseBuffer(buffer);
}
}
/* Clean up. */
if (vmbuffer != InvalidBuffer)
ReleaseBuffer(vmbuffer);
relation_close(rel, AccessShareLock);
return info;
}
为啥需要可见性映射文件?
我们知道,PostgreSQL中因为其MVCC设计,需要通过vacuum来清理死元组,而vacuum操作会扫描整个表,如果一个页面中所有元组都是可见的,那么就不需要对其进行vacuum操作,为了提高效率,我们对每个页面进行标识,如果这个页没有无效元组,则可以跳过该页面,从而提高vacuum的效率,这就是可见性映射文件的作用。
|