Postgresql源码(100)Portal与事务的关系(顶层事务与子事务)

news/2024/7/9 23:23:11 标签: postgresql, 数据库, 事务, portal, 子事务

1 总结

  • portal事务有强绑定的关系,由portal->createSubid变量记录关联关系。如果为1表示顶层事务,关联的是子事务

  • 不论是顶层事务还是子事务,提交、回滚时只会处理自己创建出来的portal

    • 顶层事务会清理非活跃状态的Portal,如果Portal是活跃的会保留内存。
    • 事务直接释放portal,无论是否活跃。
  • PLpgSQL中的提交回滚,有较大限制:

    • PLpgSQL中的提交或回滚,如果call proc在事务块中,直接失败。
    • PLpgSQL中的提交或回滚,如果pl带exception,直接失败。
    • 原因是,pl中的提交或回滚不能再子事务、或事务块内,要实现的话比较复杂,需要对齐SPI与子事务portal与子事务、exprcontext与子事务等等。
  • 事务ID只增不减,可能有空隙存在,参考PushTransaction的currentSubTransactionId。

2 提交

2.1 顶层事务提交:PreCommit_Portals与子事务提交AtSubCommit_Portals

实例

CREATE  OR REPLACE PROCEDURE tproc1() 
AS $$
DECLARE
    curs1 refcursor;                       
    curs2 CURSOR FOR SELECT c1 FROM tf1;
    curs3 CURSOR (key integer) FOR SELECT * FROM tf1 WHERE c1 > key;
    
    x int;
    y tf1%ROWTYPE;
BEGIN
    open curs1 FOR SELECT * FROM tf1 WHERE c1 > 3;
    fetch curs1 into y; RAISE NOTICE 'curs1 : %', y.c3;
    fetch curs1 into y; RAISE NOTICE 'curs1 : %', y.c3;
    
    open curs2;
    fetch curs2 into x; RAISE NOTICE 'curs2 : %', x;
    fetch curs2 into x; RAISE NOTICE 'curs2 : %', x;
    
    OPEN curs3(4);  -- OPEN curs3(key := 4);
    fetch curs3 into y; RAISE NOTICE 'curs3 : %', y.c4;
    fetch curs3 into y; RAISE NOTICE 'curs3 : %', y.c4;
EXCEPTION WHEN others THEN
    RAISE NOTICE 'in caller exception';
END;
$$ LANGUAGE plpgsql;

begin;
savepoint sp1;
savepoint sp2;
call tproc1();
commit;

(1)第一次子事务提交:EXCEPTION子事务提交AtSubCommit_Portals(subid=4)

发生在call tproc1();执行完毕。

exec_stmt_block
  ReleaseCurrentSubTransaction
    CommitSubTransaction
      AtSubCommit_Portals

当前事务堆栈:

CurrentTransactionState->subTransactionId
[1,      2,          3,              4]
 |       |           |               |
top      savepoint   savepoint       exception

由于call进入SPI,当前SPI堆栈只有一层,对应到3号子事务上,因为是在3号子事务的背景下执行的CALL。_SPI_stack[0] = {connectSubid = 3}

AtSubCommit_Portals(mySubid=4) 开始处理portal

portal1 = {          // 游标 mySubid = 4 ,当前游标的portal被继承给parent portal
  name = "<unnamed portal 17>", 
  createSubid = 4,   // AtSubCommit_Portals 4-->3
  activeSubid = 4,
  createLevel = 4,   
  sourceText = "SELECT c1 FROM tf1",
  commandTag = CMDTAG_SELECT,
  strategy = PORTAL_ONE_SELECT}

… 三个游标的portal都是类似上面处理的。特殊的是顶层portal。不属于当前要释放的子事务4,不处理。

portal1 = {          // 顶层 mySubid = 3 ,当前不处理
  name = "", 
  createSubid = 3, 
  activeSubid = 3, 
  createLevel = 3,   
  sourceText = "call tproc1();",
  commandTag = CMDTAG_CALL,
  strategy = PORTAL_MULTI_QUERY}

(2)第二次子事务提交:savepoint子事务提交AtSubCommit_Portals(subid=3)

发生在commit。

exec_simple_query
  finish_xact_command
    CommitTransactionCommand
      CommitSubTransaction
        AtSubCommit_Portals

当前事务堆栈:

CurrentTransactionState->subTransactionId
[1,      2,          3]
 |       |           |
top      savepoint   savepoint

AtSubCommit_Portals(mySubid=3)开始处理portal

portal1 = {          // 游标 mySubid = 4 ,当前游标的portal被继承给parent portal
  name = "<unnamed portal 17>", 
  createSubid = 2,   // AtSubCommit_Portals 3-->2
  activeSubid = 2,   // AtSubCommit_Portals 3
  createLevel = 2,   // AtSubCommit_Portals 3-->2
  sourceText = "SELECT c1 FROM tf1",
  commandTag = CMDTAG_SELECT,
  strategy = PORTAL_ONE_SELECT}

当前堆栈Portal已经被drop了。现在PortalHashTable里面只有三个游标的Portal。

(3)第三次子事务提交:savepoint子事务提交(subid=2)

同上。

(4)顶层事务提交(subid=1)PreCommit_Portals

发生在commit。

exec_simple_query
  finish_xact_command
    CommitTransactionCommand
      CommitTransaction
        PreCommit_Portals

提交时发现3个portal,只剩游标的3个portal了。

调用PortalDrop全部释放掉。

PreCommit_Portals函数需要关注的就是,普通portal都会被drop掉。特殊保留的是hold cursor即循环语句使用的内部自建游标,还有一种就是vacuum等多事务语句。

3 回滚

3.1 顶层事务清理:AtAbort_Portals

调用位置

AbortTransaction → AtAbort_Portals

调用一次即可,用户清理顶层事务

清理逻辑

  1. 遍历PortalHashTable,拿到所有CreatePortal创建出来的Portal(两种:执行器的portal和游标的portal
  2. 标记portal状态failed,标记failed的内存会被释放掉。
    1. 情况一:elog FATAL 主动标记failed
    2. 情况二:状态是PORTAL_READY的portal
  3. 跳过一些portal,不处理。
    1. 情况一:createSubid=0 前一个事务portal,不属于自己(!=1)。
    2. 情况二:portal->autoHeld == true 专门用于循环的游标(不是用户创建的,PL自用的)。

3.2 子事务清理:AtSubAbort_Portals

调用位置

AbortSubTransaction → AtSubAbort_Portals

有两种调用场景:

  1. 事务块内一次性rollback,递归多次AtSubAbort_Portals,提交所有子事务
  2. 回滚到某一个检查点,递归指定次数AtSubAbort_Portals,只提交指定的几个子事务

清理逻辑

  1. 遍历PortalHashTable,拿到所有CreatePortal创建出来的Portal(两种:执行器的portal和游标的portal
  2. 判断创建归属:
    • 如果当前清理的子事务portal的createSubid匹配,直接清理PORTAL_READY和PORTAL_ACTIVE状态的,包括删除内存。
    • 如果当前清理的子事务portal的createSubid不匹配:
      • 判断使用归属,如果当前清理的子事务portal的activeSubid匹配,说明不是当前子事务创建的,但是被当前子事务使用了,指标记failed但不做清理,不删除内存。

4 PushTransaction与PopTransaction函数

  • 启动子事务时需要将当前事务入栈,CurrentTransactionState换成子事务的。
  • 事务和父事务由parent连接。
  • 修改这部分代码需要注意一次弹出多个事务时,currentSubTransactionId有没有正确维护。
static void
PushTransaction(void)
{
	TransactionState p = CurrentTransactionState;
	TransactionState s;

	s = (TransactionState)
		MemoryContextAllocZero(TopTransactionContext,
							   sizeof(TransactionStateData));

注意currentSubTransactionId直增不减。pop时也不减少。

	currentSubTransactionId += 1;
	if (currentSubTransactionId == InvalidSubTransactionId)
	{
		currentSubTransactionId -= 1;
		pfree(s);
		ereport(ERROR,
				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
				 errmsg("cannot have more than 2^32-1 subtransactions in a transaction")));
	}

	/*
	 * We can now stack a minimally valid subtransaction without fear of
	 * failure.
	 */
	s->fullTransactionId = InvalidFullTransactionId;	/* until assigned */
	s->subTransactionId = currentSubTransactionId;
	s->parent = p;
	s->nestingLevel = p->nestingLevel + 1;
	s->gucNestLevel = NewGUCNestLevel();
	s->savepointLevel = p->savepointLevel;
	s->state = TRANS_DEFAULT;
	s->blockState = TBLOCK_SUBBEGIN;
	GetUserIdAndSecContext(&s->prevUser, &s->prevSecContext);
	s->prevXactReadOnly = XactReadOnly;
	s->parallelModeLevel = 0;
	s->topXidLogged = false;

	CurrentTransactionState = s;


}

http://www.niftyadmin.cn/n/56945.html

相关文章

VScode 结合clangd 构建linux源代码阅读环境

1、背景介绍上一篇文章&#xff1a;VScode 结合Global构建linux源代码阅读环境 &#xff0c;介绍了在VS Code工具中通过remote-ssh远程登陆到Linux远程服务器&#xff0c;使用Global构建linux源代码阅读环境&#xff0c;对linux kernel代码进行解析&#xff0c;实现全局搜索、自…

Spring 中 ApplicationContext 和 BeanFactory 的区别

文章目录类图包目录不同国际化强大的事件机制&#xff08;Event&#xff09;底层资源的访问延迟加载常用容器类图 包目录不同 spring-beans.jar 中 org.springframework.beans.factory.BeanFactoryspring-context.jar 中 org.springframework.context.ApplicationContext 国际…

2月面经:真可惜...拿了小米的offer,字节却惨挂在三面

我是2月份参加字节跳动和华为的面试的&#xff0c;虽然我只拿下了小米的offer&#xff0c;但是我自己也满足了&#xff0c;想把经验分享出来&#xff0c;进而帮助更多跟我一样想进大厂的同行朋友们&#xff0c;希望大家可以拿到理想offer。 自我介绍 我是16年从南京工业大学毕…

基于comsol软件弯曲单模光纤模拟仿真

在本节中&#xff0c;主要基于实验室实际光纤单模圆柱光纤进行模拟&#xff0c;与comsol案例库文件在分析过程和建模有些差异&#xff1a; 模拟主要通过以下三个步骤进行&#xff1a;模型的几何构建、物理场的添加研究、结构处理分析来进行。 下面是第一步骤&#xff1a;几何…

Maven_第五章 核心概念

目录第五章 其他核心概念1、生命周期①作用②三个生命周期③特点2、插件和目标①插件②目标3、仓库第五章 其他核心概念 1、生命周期 ①作用 为了让构建过程自动化完成&#xff0c;Maven 设定了三个生命周期&#xff0c;生命周期中的每一个环节对应构建过程中的一个操作。 …

Linux fork()系统调用流程解析

1. fork()函数介绍&#xff08;百度百科&#xff09; fork系统调用用于创建一个新进程&#xff0c;称为子进程&#xff0c;它与进程&#xff08;称为系统调用fork的进程&#xff09;同时运行&#xff0c;此进程称为父进程。创建新的子进程后&#xff0c;两个进程将执行fork&…

寒假安全作业nginx-host绕过实例复现

1.测试环境搭建 LNMP架构的话&#xff0c;肯定就是linux、nginx、mysql、php四大组件。在后面的复现中我们还会用到https的一部分知识&#xff0c;故这里的nginx就需要使用虚拟主机并且配置https证书&#xff0c;且具有php解析功能。 1.1 基础nginx配置 #1.创建web目录 mkdir …

【算法基础】前缀和与差分

&#x1f63d;PREFACE&#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐ 评论&#x1f4dd;&#x1f4e2;系列专栏&#xff1a;算法&#x1f4aa;种一棵树最好是十年前其次是现在1.什么是前缀和前缀和指一个数组的某下标之前的所有数组元素的和&#xff08;包含其自身&#x…