zl程序教程

您现在的位置是:首页 >  数据库

当前栏目

Postgresql在SyncOneBuffer时,为什么可以不加锁判断页面是否为脏(race condition第三篇)

postgresql 判断 为什么 页面 是否 可以 加锁 condition
2023-06-13 09:11:01 时间

1 问题定义

  1. 在SyncOneBuffer拿到一个脏页时,决定是否需要刷脏需要拿到desc中的标志位来判断。
  2. 这里取标志位时没有加content lock,那么如果这里刚刚检查完不需要flush,马上并发一个写入把页面标记为脏了怎么办,会不会丢数据?
static int
SyncOneBuffer(int buf_id, bool skip_recently_used, WritebackContext *wb_context)
{
	BufferDesc *bufHdr = GetBufferDescriptor(buf_id);
	int			result = 0;
	uint32		buf_state;
	BufferTag	tag;

	ReservePrivateRefCountEntry();

	/*
	 * Check whether buffer needs writing.
	 *
	 * We can make this check without taking the buffer content lock so long
	 * as we mark pages dirty in access methods *before* logging changes with
	 * XLogInsert(): if someone marks the buffer dirty just after our check we
	 * don't worry because our checkpoint.redo points before log record for
	 * upcoming changes and so we are not required to write such dirty buffer.
	 */
	buf_state = LockBufHdr(bufHdr);

	if (BUF_STATE_GET_REFCOUNT(buf_state) == 0 &&
		BUF_STATE_GET_USAGECOUNT(buf_state) == 0)
	{
		result |= BUF_REUSABLE;
	}
	else if (skip_recently_used)
	{
		/* Caller told us not to write recently-used buffers */
		UnlockBufHdr(bufHdr, buf_state);
		return result;
	}

	if (!(buf_state & BM_VALID) || !(buf_state & BM_DIRTY))
	{
		/* It's clean, so nothing to do */
		UnlockBufHdr(bufHdr, buf_state);
		return result;
	}

	/*
	 * Pin it, share-lock it, write it.  (FlushBuffer will do nothing if the
	 * buffer is clean by the time we've locked it.)
	 */
	PinBuffer_Locked(bufHdr);
	LWLockAcquire(BufferDescriptorGetContentLock(bufHdr), LW_SHARED);

	FlushBuffer(bufHdr, NULL);

	LWLockRelease(BufferDescriptorGetContentLock(bufHdr));

	tag = bufHdr->tag;

	UnpinBuffer(bufHdr, true);

	ScheduleBufferTagForWriteback(wb_context, &tag);

	return result | BUF_WRITTEN;
}

2 场景举例

正常场景:heap_insert中先标记buffer为脏,后写insert的XLOG。

buffer标记脏在写xlog前,那么如果checkpoint在sync时没发现buffer为脏:

  • 那么一定可以得出结论:insert的xlog还没写。
  • 进一步可以得出结论:checkpoint的redo稳点一定在insert xlog位点之前。
  • 进一步:这次检查点的redo位点包含这次插入的xlog。

错误场景:heap_insert中先写insert的XLOG,后标记buffer为脏。

buffer标记脏在写xlog后,那么如果checkpoint在sync时没发现buffer为脏:

  • 存在可能性:插入的xlog已经在很早前就写了,但是一直没有标记。checkpoint的刷脏环节漏掉了这个buffer。
  • 所以:redo位点在插入的xlog位点后,redo位点无法覆盖这次插入。
  • 结论:这个检查点存在问题,如果按这个检查点恢复,redo位点后都做完了,也没有做到刚才insert的xlog;并且数据也没有sync到磁盘上,这个insert的数据彻底丢失了!