pg事务:快照

news2024/9/20 23:23:41

pg中的快照

快照(snapshot)是记录数据库当前瞬时状态的一个数据结构。pg数据库的快照保存当前所有活动事务的最小事务ID、最大事务ID、当前活跃事务列表、当前事务的command id等
快照数据保存在SnapshotData结构体类型中,源码src/include/utils/snapshot.h

typedef struct SnapshotData
{
	SnapshotType snapshot_type; /* 快照类型 */
TransactionId xmin;			/* 事务ID小于xmin,对于快照可见 */
TransactionId xmax;			/* 事务ID大于xmax,对于快照不可见 */

/* 获取快照时活跃事务列表。该列表仅包括xmin与xmax之间的txid */
TransactionId *xip;
uint32		xcnt;			/* xip_list保存在xip[] */

/* 获取快照时活跃子事务列表 */
TransactionId *subxip;
int32		subxcnt;		/* 子事务保存在subxip[] */
bool		suboverflowed;	/* 子事务是否溢出,子事务较多时会产生溢出 */

bool		takenDuringRecovery;	/*  是否是恢复快照recovery-shaped snapshot? */
bool		copied;			/* 这里应该是快照是否是copy的(可重复读和串行化隔离级别,会copy快照)false if it's a static snapshot */

CommandId	curcid;			/* 事务中的command id,CID< curcid的可见 */
...
TimestampTz whenTaken;		/* 生成快照的时间戳 */
XLogRecPtr	lsn;			/* 生成快照的LSN */
} SnapshotData;
typedef struct SnapshotData *Snapshot;

快照中最重要的信息是xminxmaxxip_list。通过pg_current_snapshot()(pg12及以前用 txid_current_snapshot () )显示当前事务的快照。

注意区分快照xmin、xmax跟元组上的xmin、xmax,含义是不一样的。

lzldb=*# select pg_current_snapshot();
 pg_current_snapshot 
---------------------
 100:104:100,102
xmin最早活跃的txid,所有比他更早的事务txid<xmin,要么提交和可见,要么回滚并成为死元组
xmax第一个尚未分配的txid,xmax=latestCompletedXid+1,所有txid>=xmax的事务都未启动并对当前快照不可见
xip_listxip_list存储在数组xip[]中。因为所有事务开始顺序性和完成顺序不一定是一致的,晚开始的事务可能早完成,所以只有xmin和xmax不能完全表达获取快照时的所有活动事务。xip_list保存获得快照时的活动事务

在这里插入图片描述

快照类型

除了mvcc快照以外,pg在src/include/utils/snapshot.h中还定义了一些其他的快照类型

typedef enum SnapshotType
{
	/* 当且仅当元组符合mvcc快照可见规则时,元组可见
  * 最重要的一种快照事务,是pg用来实现mvcc的快照类型
  * 元组可见性基于事务快照的xmin,xmax,xip_list,curcid等信息进行判断
  *  如果命令发生了数据变更,当前mvcc快照是看不到的,需要再生成mvcc快照
  */
SNAPSHOT_MVCC = 0,
/* 元组上的事务已提交,则可见
  * 进行中的事务不可见
  * 命令发生了数据变更,当前self快照可以看见
 */
SNAPSHOT_SELF,

/*
 * 任何元组都可见
 */
SNAPSHOT_ANY,

/*
 * toast重要是有效的就可见。toast可见性依赖主表的元组可见性
 */
SNAPSHOT_TOAST,

/*
 * 命令发生了数据变更,当前dirty快照可以看见
 * dirty快照会保存当前进行中元组的版本信息
 * 快照xmin会设置成其他进行中事务的元组xmin,xmax类似
 */
SNAPSHOT_DIRTY,

/* HISTORIC_MVCC快照规则与MVCC快照一致,用于逻辑解码
 */
SNAPSHOT_HISTORIC_MVCC,

/*
	判断死元组是否对一些事务可见
 */
SNAPSHOT_NON_VACUUMABLE
} SnapshotType;

快照与隔离级别

不同的隔离级别,快照获取方式是不一样的

在这里插入图片描述

rc模式需要事务中的每个sql都获得快照,而rr模式在事务中只使用一个快照。获得快照的方法在GetTransactionSnapshot()函数中。

进程上的事务结构体

pg在获得快照数据的时候,需要检索所有backend进程的事务状态。

所以在理解获得快照数据函数GetSnapshotData()之前,需要先理解几个在关于backend process的结构体。这些结构体包括PGPROC、PGXACT、PROC_HDR(PROCGLOBAL)、ProcArray

这些process相关结构体包含一些进程、锁等信息,这里只研究process里事务相关的信息。源码以pg13源码为示例

PGPROC结构体

源码src/include/storage/proc.h

//每个backend进程在内存中都存储PGPROC结构体
//可以理解为backend进程的主结构体
struct PGPROC
{
...
LocalTransactionId lxid;	/* local id of top-level transaction currently
								 * being executed by this proc, if running;
								 * else InvalidLocalTransactionId */
...
struct XidCache subxids;	/* 缓存子事务XIDs */
...
/* clog组事务状态更新 */
bool		clogGroupMember;	/* 当前proc是否使用clog组提交 */
pg_atomic_uint32 clogGroupNext; /* 原子int,指向下一个组成员proc */
TransactionId clogGroupMemberXid;	/* 当前要提交的xid */
XidStatus	clogGroupMemberXidStatus;	/* 当前要提交xid的状态 */
int			clogGroupMemberPage;	/* 当前要提交xid属于哪个page*/
									
XLogRecPtr	clogGroupMemberLsn; /* 当前要提交的xid的commit日志的lsn号 */
};
/* NOTE: "typedef struct PGPROC PGPROC" appears in storage/lock.h. 居然不跟结构体写在一起*/

PGXACT结构体

//在9.2以前,PGXACT的信息在PGPROC中,由于压测显示在多cpu系统中,因为减少了获取的缓存行数,把两者分开GetSnapshotData会更快,
typedef struct PGXACT
{
	TransactionId xid;			/* id of top-level transaction currently being
								 * executed by this proc, if running and XID
								 * is assigned; else InvalidTransactionId */
								// 看上是当前进程的xmax

	TransactionId xmin;			/* 不包括lazy vaccum,事务开始时最小xid,vacuum无法删除xid >= xmin的元组*/
	uint8		vacuumFlags;	/* vacuum-related flags, see above */
	bool		overflowed;  //PGXACT是否溢出

	uint8		nxids;
} PGXACT;

能看出pgxact保存的信息比较简单,是backend的xmin、xmax等事务相关信息。而pgproc更倾向于保存backend的基本信息,pgproc中还是有一部分不太频繁调用的事务信息,不过最核心的进程事务信息在pgxact中

PROC_HDR(PROCGLOBAL)结构体

每个backend process都有proc结构体,很明显在高并发场景下扫描所有proc寻找事务信息比较耗时,这时需要一个实例级别的结构体存储所有proc信息,这个结构体就是PROCGLOBAL**。**

源码一般用结构体类型PROC_HDR定义结构体指针指向PROCGLOBAL。PROC_HDR存储的是全局的proc信息,所有proc数组列表、空闲proc等等

源码位置src/include/storage/proc.h

typedef struct PROC_HDR
{
	/* pgproc数组 (not including dummies for prepared txns) */
	PGPROC	   *allProcs;
	/* pgxact数组 (not including dummies for prepared txns) */
	PGXACT	   *allPgXact;
	...
	/* Current shared estimate of appropriate spins_per_delay value */
	int			spins_per_delay;
	/* The proc of the Startup process, since not in ProcArray */
	PGPROC	   *startupProc;
	int			startupProcPid;
	/* Buffer id of the buffer that Startup process waits for pin on, or -1 */
	int			startupBufferPinWaitBufId;
} PROC_HDR;

PROCARRAY结构体

procarray在procarray.c中,procarray.c是维护所有backend的PGPROC和PGXACT结构的。

源码位置src/backend/storage/ipc/procarray.c

typedef struct ProcArrayStruct
{
	int			numProcs;		/* proc的个数*/
	int			maxProcs;		/* proc array的大小 */

	//处理已分配的xid
	int			maxKnownAssignedXids;	/* allocated size of array */
	int			numKnownAssignedXids;	/* current # of valid entries */
	int			tailKnownAssignedXids;	/* index of oldest valid element */
	int			headKnownAssignedXids;	/* index of newest element, + 1 */
	slock_t		known_assigned_xids_lck;	/* protects head/tail pointers */

	/*
	 * Highest subxid that has been removed from KnownAssignedXids array to
	 * prevent overflow; or InvalidTransactionId if none.  We track this for
	 * similar reasons to tracking overflowing cached subxids in PGXACT
	 * entries.  Must hold exclusive ProcArrayLock to change this, and shared
	 * lock to read it.
	 */
	TransactionId lastOverflowedXid;

	/* oldest xmin of any replication slot */
	TransactionId replication_slot_xmin;
	/* oldest catalog xmin of any replication slot */
	TransactionId replication_slot_catalog_xmin;

	/* pgprocnos,相当于allPgXact[]数组下标,可用于检索allPgXact[],该数组有PROCARRAY_MAXPROCS条目 */
	int			pgprocnos[FLEXIBLE_ARRAY_MEMBER];
} ProcArrayStruct;
static ProcArrayStruct *procArray;

获得快照

GetTransactionSnapshot()

通过函数GetTransactionSnapshot()获得快照

源码src/backend/utils/time/snapmgr.c

// GetTransactionSnapshot()为一个事务中的sql分配合适的快照
Snapshot
GetTransactionSnapshot(void)
{

	 // 如果是逻辑解码,则获得historic类型快照Return historic snapshot if doing logical decoding. We'll never need a
	 // 因为是逻辑解码事务,后续就不需要再call非historic类型快照了,直接return
	if (HistoricSnapshotActive())
	{
		Assert(!FirstSnapshotSet);
		return HistoricSnapshot;
	}

	/* 如果不是事务的第一次调用,则进入if */
	if (!FirstSnapshotSet)
	{
		/*
		 * 保证catalog快照是新的
		 */
		InvalidateCatalogSnapshot();

		Assert(pairingheap_is_empty(&RegisteredSnapshots));
		Assert(FirstXactSnapshot == NULL);
	//如果是并行模式下则返回报错
		if (IsInParallelMode())
			elog(ERROR,
				 "cannot take query snapshot during a parallel operation");

		 //如果是可重复读或串行化隔离级别,则在事务中都使用同一个快照,所以只copy一次
		 //IsolationUsesXactSnapshot()标识隔离级别为可重复读或串行化,他们的在同事务中只使用一个快照
		if (IsolationUsesXactSnapshot())
		{
			//首先,在CurrentSnapshotData中创建快照 
			//如果是SI隔离级别,初始化SSI所需的数据结构
			if (IsolationIsSerializable())  
				CurrentSnapshot = GetSerializableTransactionSnapshot(&CurrentSnapshotData);
			else
				CurrentSnapshot = GetSnapshotData(&CurrentSnapshotData);
			/* Make a saved copy */
			/* 可重复读或串行化隔离级别,这个快照会贯穿整个事务,所以只复制一次 */
			CurrentSnapshot = CopySnapshot(CurrentSnapshot);
			FirstXactSnapshot = CurrentSnapshot;
			/* Mark it as "registered" in FirstXactSnapshot */
			FirstXactSnapshot->regd_count++;
			pairingheap_add(&RegisteredSnapshots, &FirstXactSnapshot->ph_node);
		}
		else
			//如果是读已提交隔离级别,获得快照
			CurrentSnapshot = GetSnapshotData(&CurrentSnapshotData);

// 修改标记,表示是第一次获得的快照,下次事务再调用该函数,就不会进到这层if了
		FirstSnapshotSet = true;
		return CurrentSnapshot;
	}

//如果不是事务中第一次调用(已经有第一个快照了)
//可重复读或串行化隔离级别,返回第一个快照的复制品
	if (IsolationUsesXactSnapshot())
		return CurrentSnapshot;

	/* Don't allow catalog snapshot to be older than xact snapshot. */
	InvalidateCatalogSnapshot();
	//读已提交级别,重新获得快照
	CurrentSnapshot = GetSnapshotData(&CurrentSnapshotData);

	return CurrentSnapshot;
}

关于IsolationUsesXactSnapshot()IsolationIsSerializable()

src/include/access/xact.h宏定义

#define XACT_READ_UNCOMMITTED	0
#define XACT_READ_COMMITTED	1
#define XACT_REPEATABLE_READ	2
#define XACT_SERIALIZABLE	3
//内部只有3个隔离级别,就是1、2、3
//2个隔离级别在每个事务中用同一快照,其他隔离级别在每个sql语句用一个快照
#define IsolationUsesXactSnapshot() (XactIsoLevel >= XACT_REPEATABLE_READ)
#define IsolationIsSerializable() (XactIsoLevel == XACT_SERIALIZABLE)

IsolationUsesXactSnapshot()是可重复读或串行化隔离级别

IsolationIsSerializable()是串行化隔离级别。

GetTransactionSnapshot()函数流程图:

在这里插入图片描述
(图片来自csdn https://blog.csdn.net/Hehuyi_In)

GetTransactionSnapshot()主要的判断逻辑:

  • 逻辑解码时的historic快照直接返回快照结果
  • 在可重复读或串行化隔离级别,如果是第一次调用,返回快照并复制,以便下次(既非第一次)直接引用该快照
  • 在读已提交隔离级别,每次调用都生成新快照
  • 串行化隔离级别的第一次调用,额外获得SSI数据信息
  • GetTransactionSnapshot()获得快照,其获得快照数据调用的是GetSnapshotData()

GetSnapshotData()

源码src/backend/storage/ipc/procarray.c

Snapshot
GetSnapshotData(Snapshot snapshot)
{
	//先初始化一些变量,包括arrayP指针,procarray,xmin,xmax,复制槽事务id等等
	ProcArrayStruct *arrayP = procArray;
	TransactionId xmin;
	TransactionId xmax;
	TransactionId globalxmin;
	int			index;
	int			count = 0;
	int			subcount = 0;
	bool		suboverflowed = false;
	TransactionId replication_slot_xmin = InvalidTransactionId;
	TransactionId replication_slot_catalog_xmin = InvalidTransactionId;

	Assert(snapshot != NULL);

	if (snapshot->xip == NULL)
	{
		/*
		 * First call for this snapshot. Snapshot is same size whether or not
		 * we are in recovery, see later comments.
		 */
		snapshot->xip = (TransactionId *) //获得当前事务的xip
			malloc(GetMaxSnapshotXidCount() * sizeof(TransactionId));
		...
		Assert(snapshot->subxip == NULL);
		snapshot->subxip = (TransactionId *) //获得当前子事务的subxip
			malloc(GetMaxSnapshotSubxidCount() * sizeof(TransactionId));
		...
	}

	//获取procarray,需要共享lwlock锁
	LWLockAcquire(ProcArrayLock, LW_SHARED);

	/* xmax=最大完成xid+1 */
	xmax = ShmemVariableCache->latestCompletedXid;
	Assert(TransactionIdIsNormal(xmax));
	TransactionIdAdvance(xmax);  //xmax+1

	/* xmax的值已经取出,xmin需要检索pgproc、pgxact、procarray */
	/* 先把globalxmin、xmin赋值xmax,如果判断backend没有事务信息,就比较好办了 */
	globalxmin = xmin = xmax; 
	
	//恢复快照单独处理
	snapshot->takenDuringRecovery = RecoveryInProgress();
	//非恢复快照需要到backend中获取事务信息
	if (!snapshot->takenDuringRecovery)
	{
		int		   *pgprocnos = arrayP->pgprocnos;
		int			numProcs;

		/*
		 * Spin over procArray checking xid, xmin, and subxids.  The goal is
		 * to gather all active xids, find the lowest xmin, and try to record
		 * subxids.看上去在检索procarray的时候会spin,以收集所有活跃的xid,最小的xmin,子事务subxid
		 */
		numProcs = arrayP->numProcs;
		for (index = 0; index < numProcs; index++)
		{
			int			pgprocno = pgprocnos[index]; //通过循环numProcs进程个数,取pgprocno全部下标
			PGXACT	   *pgxact = &allPgXact[pgprocno]; //通过pgprocno遍历所有pgxact结构体
			TransactionId xid;
			...
			/* Update globalxmin to be the smallest valid xmin */
			xid = UINT32_ACCESS_ONCE(pgxact->xmin);
			if (TransactionIdIsNormal(xid) &&
				NormalTransactionIdPrecedes(xid, globalxmin))
				globalxmin = xid;

			/* Fetch xid just once - see GetNewTransactionId */
			xid = UINT32_ACCESS_ONCE(pgxact->xid);
			...
			/* 把backend中的xmin保存到快照xip中 */
			/* 也就是说通过便利所有pgxact以找到所有活跃的xid */
			snapshot->xip[count++] = xid;
			...
			/* 子事务信息处理 */
			if (!suboverflowed) //如果子事务没有溢出
			{
				if (pgxact->overflowed)
					suboverflowed = true;  //如果事务溢出,将子事务也标记为溢出
				else
				{
					int			nxids = pgxact->nxids;

					if (nxids > 0)
					{
						PGPROC	   *proc = &allProcs[pgprocno];

						pg_read_barrier();	/* pairs with GetNewTransactionId */

						memcpy(snapshot->subxip + subcount,
							   (void *) proc->subxids.xids,
							   nxids * sizeof(TransactionId));
						subcount += nxids;
					}
				}
			}
		}
	}
	else //这里的else对应if (!snapshot->takenDuringRecovery)
	{
		// 这里的判断都是standby的,当实例是hot standby模式,从库中有查询事务时
		subcount = KnownAssignedXidsGetAndSetXmin(snapshot->subxip, &xmin,
												  xmax);

		if (TransactionIdPrecedesOrEquals(xmin, procArray->lastOverflowedXid))
			suboverflowed = true;
	}


	//事物槽的xmin和catalog全集群xmin,先保存到本地变量
	//事物槽xmin是为了防止元组被回收
	//注释中说明是为了不长时间持有ProcArrayLock,才保存到本地变量
	replication_slot_xmin = procArray->replication_slot_xmin;
	replication_slot_catalog_xmin = procArray->replication_slot_catalog_xmin;
	
	//从backend中获取事务信息的工作已经完成,下面是一堆if判断,收尾工作并增加代码严谨性 
	if (!TransactionIdIsValid(MyPgXact->xmin))
		MyPgXact->xmin = TransactionXmin = xmin;

	LWLockRelease(ProcArrayLock); //释放ProcArrayLock

	if (TransactionIdPrecedes(xmin, globalxmin))
		globalxmin = xmin; //globalxmin和进程xmin,globalxmin赋值更小的那个

	RecentGlobalXmin = globalxmin - vacuum_defer_cleanup_age;
	if (!TransactionIdIsNormal(RecentGlobalXmin))
		RecentGlobalXmin = FirstNormalTransactionId; //特殊情况下,如果RecentGlobalXmin<=2,赋值3

	/* Check whether there's a replication slot requiring an older xmin. */
	if (TransactionIdIsValid(replication_slot_xmin) &&
		NormalTransactionIdPrecedes(replication_slot_xmin, RecentGlobalXmin))
		RecentGlobalXmin = replication_slot_xmin;

	/* Non-catalog tables can be vacuumed if older than this xid */
	RecentGlobalDataXmin = RecentGlobalXmin;

	//再次检查和对比catalog,globalxminn
	if (TransactionIdIsNormal(replication_slot_catalog_xmin) &&
		NormalTransactionIdPrecedes(replication_slot_catalog_xmin, RecentGlobalXmin))
		RecentGlobalXmin = replication_slot_catalog_xmin;

	RecentXmin = xmin;
	
	//开始给snapshot结构体赋值,返回快照数据
	snapshot->xmin = xmin;
	snapshot->xmax = xmax;
	snapshot->xcnt = count;
	snapshot->subxcnt = subcount;
	snapshot->suboverflowed = suboverflowed;

	snapshot->curcid = GetCurrentCommandId(false);

	//如果是一个新快照,初始化一些快照信息
	snapshot->active_count = 0;
	snapshot->regd_count = 0;
	snapshot->copied = false;
	
	//下面是快照过久时的判断,居然写在这
	if (old_snapshot_threshold < 0)
	{
		/*
		 * If not using "snapshot too old" feature, fill related fields with
		 * dummy values that don't require any locking.
		 */
		//如果没有使用old_snapshot_threshold参数(参数<0,不会出现snapshot too old的问题)
		//赋一些简单的值,都是常量,不会产生任何锁
		snapshot->lsn = InvalidXLogRecPtr;
		snapshot->whenTaken = 0;
	}
	else
	{
		//当old_snapshot_threshold参数>=0时,需要完成old snapshot的逻辑
		snapshot->lsn = GetXLogInsertRecPtr();  //获得lsn
		snapshot->whenTaken = GetSnapshotCurrentTimestamp(); //获得快照时间
		MaintainOldSnapshotTimeMapping(snapshot->whenTaken, xmin);  //
		//GetXLogInsertRecPtr(),GetSnapshotCurrentTimestamp() ,MaintainOldSnapshotTimeMapping()三个函数中有           //SpinLockAcquire和SpinLockRelease
		//MaintainOldSnapshotTimeMapping()函数还有LWLockAcquire和LWLockRelease 
		//因为每次快照都要调用,获取快照数据函数应该是很频繁的
		//所以能看出来pg13源码中,如果将old_snapshot_threshold设置为负数,spinlock和lwlock会少很多
	}

	return snapshot;
}

pg14对事务的优化

pg14事务优化源码分析

pg13的源码能看出来GetSnapshotData()中写死了old_snapshot_threshold>=0时,每次获得快照数据都会产生较多的SpinLockLWLock,而获得快照对于数据库来说是非常频繁的操作,这必定导致一些性能问题。所以pg14中直接把old_snapshot_threshold部分删除了···

除了删除GetSnapshotData()中的old_snapshot_threshold逻辑,还做了很多其他优化:

  • 移除RecentGlobalXminRecentGlobalDataXmin,新增GlobalVisTest*系列函数

  • 新增边界boundaries概念,有两个边界分别为definitely_needed,maybe_needed

    struct GlobalVisState
    {
    	/* XIDs >= are considered running by some backend */
    	// >=definitely_needed的行一定可见
    	FullTransactionId definitely_needed;
    
    	/* XIDs < are not considered to be running by any backend */
    	// <maybe_needed的行一定可以清理
    	FullTransactionId maybe_needed;
    };
    
  • 新增ComputeXidHorizons()用于进一步精准计算horizons(保存xmin和removable xid信息),该函数仍需要遍历PGPROC。计算的范围当然是在XID >= maybe_needed && XID < definitely_needed

  • 新增GlobalVisTestShouldUpdate()用于判断是否需要再次计算边界

    先了解一个变量ComputeXidHorizonsResultLastXmin

    static TransactionId ComputeXidHorizonsResultLastXmin; //最后一次精准计算的xmin
    
    GlobalVisTestShouldUpdate(GlobalVisState *state)
    {
    	//如果xmin=0,需要重新计算边界。相当于给初始化数据库产生的元组设置一个例外判断
    	if (!TransactionIdIsValid(ComputeXidHorizonsResultLastXmin))
    		return true;
    
    	/*
    	 * If the maybe_needed/definitely_needed boundaries are the same, it's
    	 * unlikely to be beneficial to refresh boundaries.
    	 */
    	//maybe_needed等于definitely_needed不需要再计算了
    	//不过不是用的等于,而是maybe_needed>=definitely_needed
    	//“大于”的场景是没有行一定可见,“等于”的场景是只有一行一定可见
    	if (FullTransactionIdFollowsOrEquals(state->maybe_needed,
    										 state->definitely_needed))
    		return false;
    
    	/* does the last snapshot built have a different xmin? */
    	//当最后一次快照snapshot->xmin=最后一次精准计算的xmin时,不再重新计算边界
    	return RecentXmin != ComputeXidHorizonsResultLastXmin;
    }
    

可以看出maybe_needed和definitely_needed跟快照xmin、xmax是相似的,多嵌套了1层计算。先计算boundaries,再进一步精确计算horizons。GlobalVisTestShouldUpdate减少了计算boundaries的场景,而ComputeXidHorizons()精准计算也更高效。

优化结果

推荐一篇pg快照优化的文章:

https://techcommunity.microsoft.com/t5/azure-database-for-postgresql/improving-postgres-connection-scalability-snapshots/ba-p/1806462

对比优化前后的效果相当明显[外链图片转存失败,源站可能有防盗链机制,
在这里插入图片描述

其实在pg13的生产上也能看到GetSnapshotData的性能消耗总是很高。不过没截图,再借用下大佬的图

在这里插入图片描述

reference

books:
《postgresql指南 内幕探索》
《postgresql实战》
《postgresql技术内幕 事务处理深度探索》
《postgresql数据库内核分析》
https://edu.postgrespro.com/postgresql_internals-14_parts1-2_en.pdf
官方资料:
https://en.wikipedia.org/wiki/Concurrency_control
https://wiki.postgresql.org/wiki/Hint_Bits
https://www.postgresql.org/docs/current/routine-vacuuming.html#VACUUM-FOR-WRAPAROUND
https://www.postgresql.org/docs/10/storage-page-layout.html
https://www.postgresql.org/docs/13/pageinspect.html3
pg事务必读文章 interdb
https://www.interdb.jp/pg/pgsql05.html
https://www.interdb.jp/pg/pgsql06.html
源码大佬
https://blog.csdn.net/Hehuyi_In/article/details/102920988
https://blog.csdn.net/Hehuyi_In/article/details/127955762
https://blog.csdn.net/Hehuyi_In/article/details/125023923
pg的快照优化性能对比
https://techcommunity.microsoft.com/t5/azure-database-for-postgresql/improving-postgres-connection-scalability-snapshots/ba-p/1806462
其他资料
https://brandur.org/postgres-atomicity
https://mp.weixin.qq.com/s/j-8uRuZDRf4mHIQR_ZKIEg

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/548066.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PyQt5桌面应用开发(16):定制化控件-QPainter绘图

本文目录 PyQt5桌面应用系列画画图&#xff0c;喝喝茶QPainter和QPixmapQPixmapQPainter绘制事件 一个魔改的QLabelCanvas类主窗口主程序&#xff1a; 总结 PyQt5桌面应用系列 PyQt5桌面应用开发&#xff08;1&#xff09;&#xff1a;需求分析 PyQt5桌面应用开发&#xff08;2…

深入了解vector

vector 1. vector的介绍及使用1.1 vector的介绍1.2 vector的使用1.2.1 vector的定义&#xff08;(constructor)构造函数声明&#xff09;1.2.2 vector iterator 的使用1.2.3 vector Capacity1.2.4 vector Modifiers1.2.4 vector 迭代器失效问题 2. vector模拟实现 1. vector的介…

快速排序的三种方法

今日复习了一下快速排序的算法。 hoare法 快速排序由Hoare在1960年提出。它的基本思想是&#xff1a;通过排序将需要排序的数据分割成独立的两部分&#xff0c;左边的所有数据都比右边的小&#xff0c;然后再按此方法对这两部分数据分别进行快速排序递归&#xff0c;使其变成有…

时间序列预测 | 基于秃鹰算法优化BP神经网络(BES-BP)的时间序列预测,matlab代码

文章目录 效果一览文章概述部分源码参考资料效果一览 文章概述 基于秃鹰算法优化BP神经网络(BES-BP)的时间序列预测,matlab代码 评价指标包括:R2、MAE、MSE、RMSE等,代码质量极高,方便学习和替换数据。 部分源码 %% 清空环境变量 warning off % 关闭报警信息…

BurpSuite—-Spider模块(蜘蛛爬行)

本文主要介绍BurpSuite—-Spider模块(蜘蛛爬行)的相关内容 关于BurpSuite的安装可以看一下之前这篇文章&#xff1a; http://t.csdn.cn/0Qw2n 一、简介 Burp Spider 是一个映射 web 应用程序的工具。它使用多种智能技术对一个应用程序的内容和功能进行全面的清查。 Burp Spi…

基于Qt+FFmpeg的视频监控系统

github源码 需求分析 假设一个业务场景&#xff1a;每个员工工位旁有两个网络摄像头。老板需要一个员工监控软件&#xff0c;在上班时软件可以拉取RTSP视频流&#xff0c;也可以随时录制视频。这样老板就可以知道谁在摸鱼了 ◕‿◕ 为防有人上纲上线&#xff0c;在此特别声明…

【Redis】聊一下缓存双写一致性

缓存虽然可以提高查询数据的的性能&#xff0c;但是在缓存和数据 进行更新的时候 其实会出现数据不一致现象&#xff0c;而这个不一致其实可能会给业务来带一定影响。无论是Redis 分布式缓存还是其他的缓存机制都面临这样的问题。 数据不一致是如何发生&#xff1f; 数据一致…

【c语言】文件复制原理

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; &#x1f525;c语言系列专栏&#xff1a;c语言之路重点知识整合 &#x…

pg事务:事务ID

事务ID pg中每个事务都会分配事务ID&#xff0c;事务ID分为虚拟事务ID和持久化事务ID&#xff08;transactionID&#xff09;。pg的事务ID非常重要&#xff0c;是理解事务、数据可见性、事务ID回卷等等的重要知识点。 虚拟事务ID 只读事务不会分配事务ID&#xff0c;事务ID是…

【我的C++入门之旅】(下)

前言 参考前章内容【我的C入门之旅】(上) 目录 前言1.引用常引用传值、传引用效率比较引用和指针的区别 2.auto关键字使用场景 3.范围for 语法糖4.inline函数5.指针空值nullptr 1.引用 取别名&#xff0c;一块空间有多个名字或者说是一个变量有多个名字 比如&#xff1a;李逵&…

【LeetCode: 44. 通配符匹配 | 暴力递归=>记忆化搜索=>动态规划 】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

【Linux】线程详解之线程概念

前言 在我们的教材中&#xff0c;对线程给出以下的概念&#xff1a; 是进程内部的一个执行分支&#xff0c;在进程的内部运行&#xff0c;属于进程的一部分&#xff0c;比进程更加轻量化。 可能有的人看完之后都是懵的&#xff0c;什么叫在进程的内部运行&#xff0c;什么又是…

【正点原子STM32连载】 第十二章 SYSTEM文件夹介绍 摘自【正点原子】STM32F103 战舰开发指南V1.2

1&#xff09;实验平台&#xff1a;正点原子stm32f103战舰开发板V4 2&#xff09;平台购买地址&#xff1a;https://detail.tmall.com/item.htm?id609294757420 3&#xff09;全套实验源码手册视频下载地址&#xff1a; http://www.openedv.com/thread-340252-1-1.html 第十二…

文件夹路径保存不同,什么批量修改名称

在日常工作中不知道大家有没有遇到过&#xff0c;需要批量修改文件夹名称&#xff0c;并且文件夹保存路径不同呢&#xff0c;像这种情况到底不能批量修改呢。我也问了很多身边的朋友&#xff0c;他们有的说&#xff0c;他一般都修改保存路径是同一个&#xff0c;还很少遇到像我…

【C++ STL】 趣学stackqueuepriority_queue【对话情景版】

文章目录 &#x1f4cd;前言C STL 之 stack&queue基础知识及其模拟实现&#x1f4cd;容器适配器&#x1f388;什么是适配器&#xff1f;&#x1f388;STL标准库中stack和queue的底层结构&#x1f388;deque的简单介绍(了解)&#x1f4cc;deque的原理介绍&#x1f4cc;deque…

强化学习:基本概念

以 grid-world 为例&#xff0c;进行强化学习基础概念的介绍。如图&#xff0c;机械人处于一个网格世界中&#xff0c;不同的网格代表不同的功能&#xff0c;白色代表机械人可以自由的进入&#xff0c;黄色代表陷阱&#xff08;机械人一旦进入就会被强制返回起点&#xff09;&a…

《Reinforcement Learning: An Introduction》第1章笔记

文章目录 1.1 强化学习1.2 强化学习的例子1.3 强化学习的要素1.4 局限和范围1.5 拓展例子&#xff1a;井字游戏1.6 总结1.7 强化学习的早期历史参考资料 1.1 强化学习 强化学习是学习做什么—如何将情景映射到动作—以便最大化数字奖励信号。学习者不会被告知该采取什么动作&a…

MySQL基础(三十九)MySQL常用命令

1 MySQL常用命令 1.1 mysql 该mysql不是指mysql服务&#xff0c;而是指mysql的客户端工具。 语法 &#xff1a; mysql [options] [database]1.1. 连接选项 #参数 &#xff1a; -u, --username 指定用户名 -p, --password[name] 指定密码 -h, --hostname 指定服务器IP或域名…

计算机组成原理实验报告二-认识汇编语言

实验资料&#xff1a; https://wwpv.lanzoue.com/b05drqjef 密码:d19t 使用txt文档编写下面C源码&#xff0c;文档命名为【学号_hello.c】并使用Mingw工具&#xff08;是 Minimalist GNU for Windows的缩写&#xff09;的bin文件夹下gcc.exe带选项编译&#xff08;&#xff09…

JUC之线程池的标准创建方式

文章目录 JUC之线程池的标准创建方式核心和最大线程数量空闲时长(keepAliveTime)线程工厂(ThreadFactory)任务阻塞队列线程池的拒绝策略线程池的任务调度流程 JUC之线程池的标准创建方式 ​ 因为使用Executors快捷创建线程池在使用时会有严重的潜在问题&#xff0c;因此在实战…