PostgreSQL Vacuum---索引删除

本文详细解析了PostgreSQL中索引的删除过程,包括在Vaccum操作和索引插入时如何处理。在Vaccum期间,通过LazyVacuum遍历表的数据块,调用heap_page_prune删除元组,然后使用lazy_vacuum_index删除索引元组。在索引插入时,若页面空间不足,会尝试通过btvacuumscan和_bt_vacuum_one_page进行空间整理,标记并删除LP_DEAD的索引元组。整个过程中,通过遍历HOT链判断元组是否过期,从而标记为LP_DEAD,最后统一进行删除操作。

PostgreSQL Vacuum—索引删除

预备知识

PostgreSQL Vacuum—元组删除

PostgreSQL B+树索引—并发控制

概述

在《PostgreSQL Vacuum—元组删除》中,我们现实阐述了元组的删除过程,从中我们知道,在删除索引之前HOT链的链头元组的ItemData只能被标记为LP_DEAD来防止重用,只有当索引删除之后ItemData才能标记为LP_UNUSED以供重用。所以本章我们将来阐述如何删除索引。索引的删除有如下两个场景:

  • 用户执行Vaccum命令

  • 索引执行插入操作

    向索引插入index tuple时,如果发现待插入的页面没有足够的空闲空间,会先尝试对页面进行空间整理。

本文将重点阐述这两个场景的实现细节。

Lazy Vacuum

在《PostgreSQL Vacuum—元组删除》中我们阐述了Lazy Vaccum如何删除元组,现在我们可以来看看Lazy Vacuum如何删除索引。Vacuum操作要做的事情很多,包括删除元组、冻结事务标识、更新空间映射表、更新可见性映射表等,这里我们只关注删除元组操作。Lazy Vacuum的核心函数是lazy_scan_heap,这个函数非常长,就不看代码了,只讨论与删除相关的流程,在流程的每个步骤中给出代码位置:

  • 步骤1:遍历表的所有数据块(vacuumlazy.c line:585

  • 步骤2:对每一个块调用heap_page_prune清理过期元组(vacuumlazy.c line:911

    通过《PostgreSQL Vacuum—元组删除》我们知道,heap_page_prune完成后,块中会留下标记为LP_DEAD的元组,我们将它们称为dead tuple。

  • 步骤3:创建dead tuple数组记录表中所有元组dead tuple的tid(vacuumlazy.c line:961

  • 步骤4:删除dead tuple数组对应的索引元组(lazy_vacuum_index()函数,vacuumlazy.c line:1304

  • 步骤5:将dead tuple数组中所有元组的标识改为LP_UNUSED(lazy_vacuum_heap()函数,vacuumlazy.c line:1317

在这5个步骤中,我们需要重点关注步骤4,通过调用lazy_vacuum_index来删除索引,下面我们来看看lazy_vacuum_index的实现。

lazy_vacuum_index

lazy_vacuum_index最终会调用到btvacuumscan函数,btvacuumscan的核心代码如下:

blkno = BTREE_METAPAGE + 1;
for (;;)
{
   
   
    /* Get the current relation length */
    if (needLock)
        LockRelationForExtension(rel, ExclusiveLock);
    	num_pages = RelationGetNumberOfBlocks(rel);
    if (needLock)
        UnlockRelationForExtension(rel, ExclusiveLock);

    /* Quit if we've scanned the whole relation */
    if (blkno >= num_pages)
        break;
    /* Iterate over pages, then loop back to recheck length */
    for (; blkno < num_pages; blkno++)
    {
   
   
        btvacuumpage(&vstate, blkno, blkno);
    }
}

btvacuumpage

上述代码遍历索引的所有块,对于每个块调用btvacuumpage对块的索引元组进行删除,该函数主要有三个步骤:

  • 步骤1:遍历页面内索引元组,判断元组是否可以删除。

    判断元组删除是通过调用callback函数来实现,callback是一个函数指针,实际指向lazy_tid_reaped函数,而lazy_tid_reaped会校验index tuple的tid是否存在于dead tuple数组中(此外dead tuple数组是按照tid升序排列,所以在校验的时候会采用二分法)。

  • 步骤2:使用deletable数组记录需要删除的元组。

  • 步骤3:调用_bt_delitems_vacuum删除deletable中的元组。

btvacuumpage的实现如下:

static void
btvacuumpage(BTVacState *vstate, BlockNumber blkno, BlockNumber orig_blkno)
{
   
   
	IndexVacuumInfo *info = vstate->info;
	IndexBulkDeleteResult *stats = vstate->stats;
	IndexBulkDeleteCallback callback = vstate->callback;
	void	   *callback_state = vstate->callback_state;
	Relation	rel = info->index;
	bool		delete_now;
	BlockNumber recurse_to;
	Buffer		buf;
	Page		page;
	BTPageOpaque opaque = NULL;

restart:
	delete_now = false;
	recurse_to = P_NONE;

	/* call vacuum_delay_point while not holding any buffer lock */
	vacuum_delay_point();

	/*
	 * We can't use _bt_getbuf() here because it always applies
	 * _bt_checkpage(), which will barf on an all-zero page. We want to
	 * recycle all-zero pages, not fail.  Also, we want to use a nondefault
	 * buffer access strategy.
	 */
	buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL,
							 info->strategy);
	LockBuffer(buf, BT_READ);
	page = BufferGetPage(buf);
	if (!PageIsNew(page))
	{
   
   
		_bt_checkpage(rel, buf);
		opaque = (BTPageOpaque) PageGetSpecialPointer(page);
	}

	/*
	 * If we are recursing, the only case we want to do anything with is a
	 * live leaf page having the current vacuum cycle ID.  Any other state
	 * implies we already saw the page (eg, deleted it as being empty).
	 */
	if (blkno != orig_blkno)
	{
   
   
		if (_bt_page_recyclable(page) ||
			P_IGNORE(opaque) ||
			!P_ISLEAF(opaque) ||
			opaque->btpo_cycleid != vstate->cycleid)
		{
   
   
			_bt_relbuf(rel, buf);
			return;
		}
	}

	/* Page is valid, see what to do with it */
	if (_bt_page_recyclable(page))
	{
   
   
		/* Okay to recycle this page */
		RecordFreeIndexPage
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值