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

本文主要是介绍Postgresql源码(123)事务提交时三段资源释放分析ResourceOwnerRelease,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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
typedef enum
{RESOURCE_RELEASE_BEFORE_LOCKS = 1,RESOURCE_RELEASE_LOCKS,RESOURCE_RELEASE_AFTER_LOCKS,
} ResourceReleasePhase;

例如事务提交时CommitTransaction:

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);......

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

函数名phaseisCommitisTopLevel
CommitTransactionRESOURCE_RELEASE_BEFORE_LOCKStruetrue
CommitTransactionRESOURCE_RELEASE_LOCKStruetrue
CommitTransactionRESOURCE_RELEASE_AFTER_LOCKStruetrue
PrepareTransactionRESOURCE_RELEASE_BEFORE_LOCKStruetrue
PrepareTransactionRESOURCE_RELEASE_LOCKStruetrue
PrepareTransactionRESOURCE_RELEASE_AFTER_LOCKStruetrue
AbortTransactionRESOURCE_RELEASE_BEFORE_LOCKSfalsetrue
AbortTransactionRESOURCE_RELEASE_LOCKSfalsetrue
AbortTransactionRESOURCE_RELEASE_AFTER_LOCKSfalsetrue
CommitSubTransactionRESOURCE_RELEASE_BEFORE_LOCKStruefalse
CommitSubTransactionRESOURCE_RELEASE_LOCKStruefalse
CommitSubTransactionRESOURCE_RELEASE_AFTER_LOCKStruefalse
AbortSubTransactionRESOURCE_RELEASE_BEFORE_LOCKSfalsefalse
AbortSubTransactionRESOURCE_RELEASE_LOCKSfalsefalse
AbortSubTransactionRESOURCE_RELEASE_AFTER_LOCKSfalsefalse

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

结论

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

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

3 代码分析

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

commit b8bff07daa85c837a2747b4d35cd5a27e73fb7b2
Author: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date:   Wed Nov 8 13:30:50 2023 +0200Make ResourceOwners more easily extensible.

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

static void
ResourceOwnerReleaseInternal(ResourceOwner owner,ResourceReleasePhase phase,bool isCommit,bool isTopLevel)
{ResourceOwner child;ResourceOwner save;ResourceReleaseCallbackItem *item;ResourceReleaseCallbackItem *next;Datum		foundres;

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

	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
	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。
    • 回滚:释放锁。
	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);elseLockReleaseCurrentOwner(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。
	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;
}

这篇关于Postgresql源码(123)事务提交时三段资源释放分析ResourceOwnerRelease的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/728630

相关文章

Python实现无痛修改第三方库源码的方法详解

《Python实现无痛修改第三方库源码的方法详解》很多时候,我们下载的第三方库是不会有需求不满足的情况,但也有极少的情况,第三方库没有兼顾到需求,本文将介绍几个修改源码的操作,大家可以根据需求进行选择... 目录需求不符合模拟示例 1. 修改源文件2. 继承修改3. 猴子补丁4. 追踪局部变量需求不符合很

Spring事务中@Transactional注解不生效的原因分析与解决

《Spring事务中@Transactional注解不生效的原因分析与解决》在Spring框架中,@Transactional注解是管理数据库事务的核心方式,本文将深入分析事务自调用的底层原理,解释为... 目录1. 引言2. 事务自调用问题重现2.1 示例代码2.2 问题现象3. 为什么事务自调用会失效3

找不到Anaconda prompt终端的原因分析及解决方案

《找不到Anacondaprompt终端的原因分析及解决方案》因为anaconda还没有初始化,在安装anaconda的过程中,有一行是否要添加anaconda到菜单目录中,由于没有勾选,导致没有菜... 目录问题原因问http://www.chinasem.cn题解决安装了 Anaconda 却找不到 An

Spring定时任务只执行一次的原因分析与解决方案

《Spring定时任务只执行一次的原因分析与解决方案》在使用Spring的@Scheduled定时任务时,你是否遇到过任务只执行一次,后续不再触发的情况?这种情况可能由多种原因导致,如未启用调度、线程... 目录1. 问题背景2. Spring定时任务的基本用法3. 为什么定时任务只执行一次?3.1 未启用

C++ 各种map特点对比分析

《C++各种map特点对比分析》文章比较了C++中不同类型的map(如std::map,std::unordered_map,std::multimap,std::unordered_multima... 目录特点比较C++ 示例代码 ​​​​​​代码解释特点比较1. std::map底层实现:基于红黑

Spring、Spring Boot、Spring Cloud 的区别与联系分析

《Spring、SpringBoot、SpringCloud的区别与联系分析》Spring、SpringBoot和SpringCloud是Java开发中常用的框架,分别针对企业级应用开发、快速开... 目录1. Spring 框架2. Spring Boot3. Spring Cloud总结1. Sprin

Spring 中 BeanFactoryPostProcessor 的作用和示例源码分析

《Spring中BeanFactoryPostProcessor的作用和示例源码分析》Spring的BeanFactoryPostProcessor是容器初始化的扩展接口,允许在Bean实例化前... 目录一、概览1. 核心定位2. 核心功能详解3. 关键特性二、Spring 内置的 BeanFactory

MyBatis-Plus中Service接口的lambdaUpdate用法及实例分析

《MyBatis-Plus中Service接口的lambdaUpdate用法及实例分析》本文将详细讲解MyBatis-Plus中的lambdaUpdate用法,并提供丰富的案例来帮助读者更好地理解和应... 目录深入探索MyBATis-Plus中Service接口的lambdaUpdate用法及示例案例背景

MyBatis-Plus中静态工具Db的多种用法及实例分析

《MyBatis-Plus中静态工具Db的多种用法及实例分析》本文将详细讲解MyBatis-Plus中静态工具Db的各种用法,并结合具体案例进行演示和说明,具有很好的参考价值,希望对大家有所帮助,如有... 目录MyBATis-Plus中静态工具Db的多种用法及实例案例背景使用静态工具Db进行数据库操作插入

Seata之分布式事务问题及解决方案

《Seata之分布式事务问题及解决方案》:本文主要介绍Seata之分布式事务问题及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Seata–分布式事务解决方案简介同类产品对比环境搭建1.微服务2.SQL3.seata-server4.微服务配置事务模式1