前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Postgresql源码(123)事务提交时三段资源释放分析ResourceOwnerRelease

Postgresql源码(123)事务提交时三段资源释放分析ResourceOwnerRelease

作者头像
mingjie
发布2024-02-20 08:11:12
2040
发布2024-02-20 08:11:12
举报
文章被收录于专栏:Postgresql源码分析

0 总结

  1. 三段释放原因:因为如果先释放锁,没有释放一些共享资源(比如pin住的buffer),别人拿到锁后发现我们仍然持有一些资源,就会有问题。所以三阶段释放主要是以锁为分界线,先释放锁保护的资源,在释放锁,在清理私有资源。这样可以保证别人拿到锁后,一定也能拿到对应的资源。
  2. 三段:先放pinned buffer、relation、dsm这些共享资源;再放锁;所有放其他会话看不到的私有资源。

1 资源随事务释放

三阶段释放是指ResourceOwnerRelease函数在使用时需要调用三次,按固定顺序调用每次删除特定的资源:

  1. RESOURCE_RELEASE_BEFORE_LOCKS
  2. RESOURCE_RELEASE_LOCKS
  3. RESOURCE_RELEASE_AFTER_LOCKS
代码语言:javascript
复制
typedef enum
{
	RESOURCE_RELEASE_BEFORE_LOCKS = 1,
	RESOURCE_RELEASE_LOCKS,
	RESOURCE_RELEASE_AFTER_LOCKS,
} ResourceReleasePhase;

例如事务提交时CommitTransaction:

代码语言:javascript
复制
static void
CommitTransaction(void)
	...
	...
	...
	ResourceOwnerRelease(TopTransactionResourceOwner,
						 RESOURCE_RELEASE_BEFORE_LOCKS,
						 true, true);

	/* Check we've released all buffer pins */
	AtEOXact_Buffers(true);

	/* Clean up the relation cache */
	AtEOXact_RelationCache(true);

	/*
	 * Make catalog changes visible to all backends.  This has to happen after
	 * relcache references are dropped (see comments for
	 * AtEOXact_RelationCache), but before locks are released (if anyone is
	 * waiting for lock on a relation we've modified, we want them to know
	 * about the catalog change before they start using the relation).
	 */
	AtEOXact_Inval(true);

	AtEOXact_MultiXact();

	ResourceOwnerRelease(TopTransactionResourceOwner,
						 RESOURCE_RELEASE_LOCKS,
						 true, true);
	ResourceOwnerRelease(TopTransactionResourceOwner,
						 RESOURCE_RELEASE_AFTER_LOCKS,
						 true, true);
	...
	...

其他几个事务控制函数资源释放时,也是按照相同的顺序分三阶段释放的:

函数名

phase

isCommit

isTopLevel

CommitTransaction

RESOURCE_RELEASE_BEFORE_LOCKS

true

true

CommitTransaction

RESOURCE_RELEASE_LOCKS

true

true

CommitTransaction

RESOURCE_RELEASE_AFTER_LOCKS

true

true

PrepareTransaction

RESOURCE_RELEASE_BEFORE_LOCKS

true

true

PrepareTransaction

RESOURCE_RELEASE_LOCKS

true

true

PrepareTransaction

RESOURCE_RELEASE_AFTER_LOCKS

true

true

AbortTransaction

RESOURCE_RELEASE_BEFORE_LOCKS

false

true

AbortTransaction

RESOURCE_RELEASE_LOCKS

false

true

AbortTransaction

RESOURCE_RELEASE_AFTER_LOCKS

false

true

CommitSubTransaction

RESOURCE_RELEASE_BEFORE_LOCKS

true

false

CommitSubTransaction

RESOURCE_RELEASE_LOCKS

true

false

CommitSubTransaction

RESOURCE_RELEASE_AFTER_LOCKS

true

false

AbortSubTransaction

RESOURCE_RELEASE_BEFORE_LOCKS

false

false

AbortSubTransaction

RESOURCE_RELEASE_LOCKS

false

false

AbortSubTransaction

RESOURCE_RELEASE_AFTER_LOCKS

false

false

2 为什么要分三阶段释放?

结论

因为如果先释放锁,没有释放一些共享资源(比如pin住的buffer),别人拿到锁后发现我们仍然持有一些资源,就会有问题。所以三阶段释放主要是以锁为分界线,先释放锁保护的资源,在释放锁,在清理私有资源。这样可以保证别人拿到锁后,一定也能拿到对应的资源。

  1. RESOURCE_RELEASE_BEFORE_LOCKS(预锁定阶段):需要释放对其他后端可见的资源(pinned buffers)。为了确保当我们释放另一个后端可能正在等待的锁时,它会看到我们已经完全退出了我们的事务。这是为了防止在释放锁之后,其他后端仍然看到我们持有的资源,从而可能导致数据不一致或其他问题。
  2. RESOURCE_RELEASE_LOCKS(锁定阶段):释放持有的所有锁。这是为了让其他可能正在等待这些锁的后端能够继续执行。
  3. RESOURCE_RELEASE_AFTER_LOCKS(后锁定阶段):后端内部的清理工作。释放那些只有后端自己知道的、不会影响其他后端的资源。

3 代码分析

下面这次提交后对resowner做了扩展性增强,代码逻辑没变但可读性有点差(PG17dev分支)

代码语言:javascript
复制
commit b8bff07daa85c837a2747b4d35cd5a27e73fb7b2
Author: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date:   Wed Nov 8 13:30:50 2023 +0200

    Make ResourceOwners more easily extensible.

围绕本篇主题,后面分析这次提交前代码(PG17前的逻辑)

代码语言:javascript
复制
static void
ResourceOwnerReleaseInternal(ResourceOwner owner,
							 ResourceReleasePhase phase,
							 bool isCommit,
							 bool isTopLevel)
{
	ResourceOwner child;
	ResourceOwner save;
	ResourceReleaseCallbackItem *item;
	ResourceReleaseCallbackItem *next;
	Datum		foundres;

resowner维护树形结构,递归释放自己的child资源。

代码语言:javascript
复制
	for (child = owner->firstchild; child != NULL; child = child->nextchild)
		ResourceOwnerReleaseInternal(child, phase, isCommit, isTopLevel);
	
	...

3.1 第一阶段:RESOURCE_RELEASE_BEFORE_LOCKS

  1. 事务提交时不应该有正在进行中的io了,这里需要把标志位清理干净,避免影响后面事务使用这个buffer。
  2. 清理pinned的buffer
    • 提交时不应该有pinned的了,会告警,回滚时就无所谓了,因为错误不一定出在哪,不一定给机会清理。
  3. 关闭打开的relation
    • 提交时不应该有打开的了,会告警;回滚时同上。
  4. 关闭dsm段
  5. 关闭jit context
代码语言:javascript
复制
	if (phase == RESOURCE_RELEASE_BEFORE_LOCKS)
	{
		while (ResourceArrayGetAny(&(owner->bufferioarr), &foundres))
		{
			Buffer		res = DatumGetBuffer(foundres);

			if (isCommit)
				elog(PANIC, "lost track of buffer IO on buffer %d", res);
			AbortBufferIO(res);
		}

		while (ResourceArrayGetAny(&(owner->bufferarr), &foundres))
		{
			Buffer		res = DatumGetBuffer(foundres);

			if (isCommit)
				PrintBufferLeakWarning(res);
			ReleaseBuffer(res);
		}

		while (ResourceArrayGetAny(&(owner->relrefarr), &foundres))
		{
			Relation	res = (Relation) DatumGetPointer(foundres);

			if (isCommit)
				PrintRelCacheLeakWarning(res);
			RelationClose(res);
		}

		while (ResourceArrayGetAny(&(owner->dsmarr), &foundres))
		{
			dsm_segment *res = (dsm_segment *) DatumGetPointer(foundres);

			if (isCommit)
				PrintDSMLeakWarning(res);
			dsm_detach(res);
		}

		while (ResourceArrayGetAny(&(owner->jitarr), &foundres))
		{
			JitContext *context = (JitContext *) DatumGetPointer(foundres);

			jit_release_context(context);
		}
		
		...
		...
		}
	}

3.2 第二阶段:RESOURCE_RELEASE_LOCKS

  1. 如果是顶层事务,直接释放所有锁,具体:
    • 提交时要保留会话锁,释放事务锁。事务没了会话锁还需要继续生效,生命周期比事务长。
    • 回滚时要释放所有锁。
    • 会话锁:咨询锁。
    • 事务锁:行锁、表锁等。
  2. 如果是子事务,按提交回滚做出不同行为。
    • 提交:转移锁到parent resowner。
    • 回滚:释放锁。
代码语言:javascript
复制
	else if (phase == RESOURCE_RELEASE_LOCKS)
	{
		if (isTopLevel)
		{
			if (owner == TopTransactionResourceOwner)
			{
				ProcReleaseLocks(isCommit);
				ReleasePredicateLocks(isCommit, false);
			}
		}
		else
		{
			LOCALLOCK **locks;
			int			nlocks;

			...

			if (isCommit)
				LockReassignCurrentOwner(locks, nlocks);
			else
				LockReleaseCurrentOwner(locks, nlocks);
		}
	}

3.3 第三阶段:RESOURCE_RELEASE_AFTER_LOCKS

  1. 释放catcache系统表缓存。
  2. 释放cached plan。
  3. 释放tuple desc,注意tuple desc在构造好后,会用IncrTupleDescRefCount函数,在resowner中记录,按引用计数控制删除。引用计数的机制主要是为了处理TupleDesc在多个地方共享使用的情况。例如一个查询的多个部分可能都需要引用同一个TupleDesc。如果在一个部分结束时就直接删除TupleDesc,那么其他部分就无法继续使用这个TupleDesc了。
  4. 释放快照。
  5. 释放fd。
代码语言:javascript
复制
	else if (phase == RESOURCE_RELEASE_AFTER_LOCKS)
	{
		while (ResourceArrayGetAny(&(owner->catrefarr), &foundres))
		{
			HeapTuple	res = (HeapTuple) DatumGetPointer(foundres);

			if (isCommit)
				PrintCatCacheLeakWarning(res);
			ReleaseCatCache(res);
		}

		while (ResourceArrayGetAny(&(owner->catlistrefarr), &foundres))
		{
			CatCList   *res = (CatCList *) DatumGetPointer(foundres);

			if (isCommit)
				PrintCatCacheListLeakWarning(res);
			ReleaseCatCacheList(res);
		}

		while (ResourceArrayGetAny(&(owner->planrefarr), &foundres))
		{
			CachedPlan *res = (CachedPlan *) DatumGetPointer(foundres);

			if (isCommit)
				PrintPlanCacheLeakWarning(res);
			ReleaseCachedPlan(res, owner);
		}

		while (ResourceArrayGetAny(&(owner->tupdescarr), &foundres))
		{
			TupleDesc	res = (TupleDesc) DatumGetPointer(foundres);

			if (isCommit)
				PrintTupleDescLeakWarning(res);
			DecrTupleDescRefCount(res);
		}

		while (ResourceArrayGetAny(&(owner->snapshotarr), &foundres))
		{
			Snapshot	res = (Snapshot) DatumGetPointer(foundres);

			if (isCommit)
				PrintSnapshotLeakWarning(res);
			UnregisterSnapshot(res);
		}

		/* Ditto for temporary files */
		while (ResourceArrayGetAny(&(owner->filearr), &foundres))
		{
			File		res = DatumGetFile(foundres);

			if (isCommit)
				PrintFileLeakWarning(res);
			FileClose(res);
		}
	}

	for (item = ResourceRelease_callbacks; item; item = next)
	{
		next = item->next;
		item->callback(phase, isCommit, isTopLevel, item->arg);
	}

	CurrentResourceOwner = save;
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-02-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 0 总结
  • 1 资源随事务释放
  • 2 为什么要分三阶段释放?
  • 3 代码分析
    • 3.1 第一阶段:RESOURCE_RELEASE_BEFORE_LOCKS
      • 3.2 第二阶段:RESOURCE_RELEASE_LOCKS
        • 3.3 第三阶段:RESOURCE_RELEASE_AFTER_LOCKS
        相关产品与服务
        腾讯云代码分析
        腾讯云代码分析(内部代号CodeDog)是集众多代码分析工具的云原生、分布式、高性能的代码综合分析跟踪管理平台,其主要功能是持续跟踪分析代码,观测项目代码质量,助力维护团队卓越代码文化。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档