Postgresql源码(27)事务提交与检查点并发场景下丢失事务状态问题分析(race condition第一篇)

感谢Serendipity_Shy指正,原来理解有点问题,20220726更新了一版。
结论在第三部分”3 为什么checkpoint需要等事务提交?“。

Postgresql事务在事务提交时(执行commit的最后阶段)会通过加锁阻塞checkpoint的执行,尽管时间非常短,下面分析为什么需要这样做? 不这样做会有什么问题。

1 提交堆栈

看一下事务提交堆栈

#1  0x0000000000539175 in CommitTransaction () at xact.c:2079
#2  0x0000000000539e04 in CommitTransactionCommand () at xact.c:2824
#3  0x000000000087d1ea in finish_xact_command () at postgres.c:2482
#4  0x000000000087af27 in exec_simple_query (query_string=0x24050e0 "insert into t1 values (1,1);") at postgres.c:1154

2 函数调用过程

关键流程


CommitTransaction
    ...
    latestXid = RecordTransactionCommit();
    ...
        BufmgrCommit()
        START_CRIT_SECTION()
        【关键流程】
        END_CRIT_SECTION()
        latestXid = TransactionIdLatest(xid, nchildren, children);
        SyncRepWaitForLSN(XactLastRecEnd, true);
        return latestXid;
    ...
    ProcArrayEndTransaction(MyProc, latestXid);
    
    ...
    // clean ...

3 关键流程

delayChkpt阻塞checkpoint发生位置:

1 事务提交配置delayChkpt

RecordTransactionCommit
  ...
  START_CRIT_SECTION();
  MyPgXact->delayChkpt = true;
  /* 写XLOG:COMMIT */
  /* 写CLOG:内存写不刷盘 */
  MyPgXact->delayChkpt = false;
  ...

2 CreateCheckPoint等待delayChkpt

联动CreateCheckPoint,会在【2】等在所有Xact的delayChkpt为false才能继续

CreateCheckPoint
  // 【1】计算位置(重要)
  WALInsertLockAcquireExclusive();
  curInsert = XLogBytePosToRecPtr(Insert->CurrBytePos);
  freespace = INSERT_FREESPACE(curInsert);
	if (freespace == 0)
	{
		if (curInsert % XLogSegSize == 0)
			curInsert += SizeOfXLogLongPHD;
		else
			curInsert += SizeOfXLogShortPHD;
	}
	checkPoint.redo = curInsert;
	RedoRecPtr = XLogCtl->Insert.RedoRecPtr = checkPoint.redo;
	WALInsertLockRelease();
  
	// 【2】通过delayChkpt等其他所有正在提交中、正在写日志的事务
	vxids = GetVirtualXIDsDelayingChkpt(&nvxids);
	if (nvxids > 0)
	{
		do
		{
			pg_usleep(10000L);	/* wait for 10 msec */
		} while (HaveVirtualXIDsDelayingChkpt(vxids, nvxids));
	}
	pfree(vxids);
  
  // 【3】刷数据
	CheckPointGuts(checkPoint.redo, flags);
  // 【4】记chkpt日志
	XLogBeginInsert();
	XLogRegisterData((char *) (&checkPoint), sizeof(checkPoint));
	recptr = XLogInsert(RM_XLOG_ID,
						shutdown ? XLOG_CHECKPOINT_SHUTDOWN :
						XLOG_CHECKPOINT_ONLINE);

	XLogFlush(recptr);

3 为什么checkpoint需要等事务提交?

3.1 没有delay锁的情况

发生按事件发生时间顺序:

  1. 事务A提交写commit日志,做flush,位点1000
  2. checkpoint触发,拿redo位点1500
  3. checkpoint刷数据,包括clog、buffer等
  4. checkpoint刷完数据,写一条XLOG记录redo位点
  5. checkpoint结束
  6. 事务A提交写xlog到内存,不做flush
  7. crash发生

redo过程:

  1. 由于检查点是正常退出的,日志完成,所以采用上述检查点。
  2. 从检查点的xlog中拿出redo位点:1500。
  3. 从1500开始重做,注意没有做到1000,现在不知道事务A是否提交。
  4. 继续重做完成,由于之间检查点构造时,flush clog时,事务A的clog还没写;所以当前磁盘上的clog并未包含事务A的信息。所以事务A的是否提交的信息丢失了。
    在这里插入图片描述

3.2 有delay锁的情况

情况一:redo位点在事务提交之前,那么redo时一定可以覆盖提交日志。

在这里插入图片描述

情况二:

刷数据在clog写完之后,那么clog一定已经落盘了。
在这里插入图片描述

总结

只要保证事务提交时,刷XLOG和写CLOG内存两个动作的原子性。

就不会被checkpoint两步动作影响(拿redo位点、刷数据)。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

高铭杰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值