--POSTGRESQL 事务控制(二) 事务开启 (写着费力,看着费劲系列)
今天接着上回书,事务如果在处理中没有子事务,则实现和控制是十分容易的,但如果有子事务的情况下, 子事务通过transactionState结构体来实现,(上次已经提到了),每一个transactionState都指向父事务的结构体的指针.
在继续往下说之前,我们的提到clog, 这是理解后面要提到的一些事情的前提, 在POSTGRESQL 中clog 是记录每一个事务相关的xid, 以及事务的提交状态, 状态包含了 执行中, 已提交, 中断,子事务已提交. 那么clog 承担了对整体事务状态的记录和查询的任务.
下面是一段clog 代码和 xidstatus的状态结构
从上面两个图中可以得到的信息
CLOG_BITS_PER_XACT 2 一个事务使用2BIT
CLOG_XACTS_PER_BYTE 4 一个字节记录4个事务
CLOG_XACTS_PER_PAGE (BLCKSZ*CLOG_XACTS_PER_BYTE) 一个页面记录(8196*4=32784) 个事务
((xid) (TransactionId) CLOG_XACTS_PER_PAGE
如果要寻找xid的页面位置,通过事务号除以一个页面的记录事务数,就得到了具体这个事务所在的页面数.
((xid) % (TransactionId) CLOG_XACTS_PER_PAGE)
下面的计算可以获得相关信息存储在页面中的第多少个字节
(TransactionIdToPgIndex(xid) / CLOG_XACTS_PER_BYTE)
后的公式是计算事务信息存在在字节内的偏移量
((xid) % (TransactionId) CLOG_XACTS_PER_BYTE)
如果要知道xid事务的状态,则直接通过取余的方式直接获得事务的状态页面内的偏移位置
从上面的东西中可以获得
1 事务的状态是通过2个BITS来存储的, 每个字节可以存储4个事务的状态
2 确认当前事务的状态通过事务ID ,事务XID 是32BIT
3 通过简单的除法和商运算,可以获得事务的状态信息
我们验证一下, 我们开通4个 SESSION ,然后将SESSION中的事务ID打印出来.
分别事务id 为 623 - 626
下面四个计算的公式
1 得到事务存储的页面 0号页面
2 得到事务存储在页面总的偏移量
3 数据存储在页面内的多少字节
4 字节内的偏移量
通过事务ID 就可以直接获得事务的状态在CLOG中的存储位置,并且提取相关的信息.
那么我们来计算一下,一个8K的页面可以存储多少事务,以及事务的状态
8K页面* 8b/2b (事务的4个状态) = 32KB = 16384 个事务的状态
假设我们有5MB的内存空间,可以存储 10485760 个事务的状态,这里需要注意我们的事务动态的信息都是存在在内存中,所以用更小的内存来控制更多的事务是有利于整体运行效率的.
现在我们回归主题,子事务的与父事务的关系的存储, 在我们的pg的数据库目录里面有一个目录是pg_subtrans 目录,其中通过pg_subtrans 这个目录中的文件来存储子事务与父事务之间的关系. 子事务保存自己上一次的事务,相关事务的追述是通过自下而上的方式.
下面是这段和pg_subtrans有关的代码, 这段代码在 xact.c 中, 这段代码的主要的作用是在给子事务上的父事务ID 进行记录, 好进行自下而上的寻根
static void
AssignTransactionId(TransactionState s)
{
bool isSubXact = (s->parent != NULL);
//是否是子事务,如果有父事务,则父事务不能为空
ResourceOwner currentOwner;
bool log_unknown_top = false;
// 确认当前事务号有效并且事务是在inprogress 的状态
Assert(!TransactionIdIsValid(s->transactionId));
Assert(s->state == TRANS_INPROGRESS);
//如果当前事务在并行则不运行分配事务号
if (IsInParallelMode() || IsParallelWorker())
elog(ERROR, "cannot assign XIDs during a parallel operation");
在 subtrans.c中包含关于在PG 重启后处理pg_subtrans中的数据的方法是直接清理掉这些数据,一下这段代码进行相关工作的代码.
主要的原由是,子事务是包含在事务内的,在事务本身失效后,这些子事务也没有必要进行记录,所以在pg_wal中也不会有相关子事务的日志记录.
上期说到事务的ID 只有在执行 INSERT ,UPDATE ,DELETE的时候才进行事务号的分配,那么不分配事务号的情况下,事务到底有没有事务号, 实际上是有的在事务开始时是分配一个虚拟的事务ID
在开始一个事务的时候,会先开始分配内存和事务所需要的,buffer, 锁等信息,每个事务都需要有相关的resourceOwner信息, 虚拟的事务主要通过两个方式来生成自己的虚拟事务ID , benchend process ID + 本地的计数器, 这样就可以产生一个自己的临时的虚拟的事务ID
在获取了ID后,我们直接就开始进行相关事务的开启,参加下面的语句
TRACE_POSTGRESQL_TRANSACTION_START(vxid.localTransactionId);
此时在设置完相关的变量的初始值后, 如事务就可以开始启动了.
总结,在一个事务开启时
1 事务初始并没有实际的事务ID ,而是本地通过backend 和计数器临时分配的虚拟事务ID , 只有在事务中出现IUD 的操作才会分配实际的事务ID
2 服务器在重启或者CRASH 后会清理与子事务与父事务相关的关联的信息
3 在事务中,存在越多的子事务,则整体的系统的性能会越来越低,所以不建议使用大量的save point 产生大量的子事务. 并且子事务与父事务之间的关系是自下而上的搜索, 只有通过子事务才能查找到自己的父事务, 并在设计的时候, 通过简单的事务ID与页面数的余数,商可以直接快速定位事务的状态.
相关文章