Postgresql源码(27)为什么事务提交会通过delayChkpt阻塞checkpoint

2022-07-14 13:43:54 浏览数 (1)

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

1 提交堆栈

看一下事务提交堆栈

代码语言:javascript复制
#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 函数调用过程

关键流程

代码语言:javascript复制
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

代码语言:javascript复制
RecordTransactionCommit
  ...
  START_CRIT_SECTION();
  MyPgXact->delayChkpt = true;
  /* 写XLOG:COMMIT */
  /* 写CLOG:内存写不刷盘 */
  MyPgXact->delayChkpt = false;
  ...

2 CreateCheckPoint等待delayChkpt

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

代码语言:javascript复制
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需要等事务提交?

原因:情况二的分析。

确定REDO位点是在createCheckpoint的函数前面执行的,checkpoint和事务提交并发会有下面三种情况发生(假设没有delayChkpt会有情况二发生)

  • 情况一:redo point在commit提交前,那么如果crash发生了,redo过程会覆盖这条xlog,不会有问题
  • 情况二:如果没有delayChkpt,redo point可能发生在上图中的位置(然后checkpoint刷完数据后,当前事务才写clog),XLOG已经先写了,如果crash发生了,redo过程不会覆盖这条xlog,而且clog信息不存在,那么commit信息彻底丢掉了。 20220704补充:检查点redo位点是先拿的,然后刷数据。问题场景是检查点拿了个redo位点,并发事务X的commit日志在位点前面,所以后面的redo不会做这条日志。然后呢,检查点拿了redo位点之后刷数据包括clog,但是数据都刷完了,事务X的clog才写下去。如果这会crash发生了,那redo过程既不会做X事务的commit日志,也不能查到X事务的clog事务信息。结果这个事务状态就永远丢掉了。
  • 情况三:redo point在事务提交后,redo时xlog虽然还是做不到,但是clog一定会被刷下去,所以我们不会丢失事务提交信息。

0 人点赞