postgresql 13.5
1 数据结构
- 快照类型有很多,但使用通用结构来管理,SnapshotSatisfiesFunc是负责处理该快照的函数。
- 快照主要记录 全局活跃事物列表中的 最小事务ID、最大事务ID、当前活跃事务列表、当前事务commandid
typedef struct SnapshotData
{
/* 处理当前快照的函数 */
SnapshotSatisfiesFunc satisfies; /* tuple test function */
/* 多版本控制 */
TransactionId xmin; /* all XID < xmin are visible to me */
TransactionId xmax; /* all XID >= xmax are invisible to me */
/* 活跃事务列表 */
TransactionId *xip;
/* 活跃事务数量 */
uint32 xcnt; /* # of xact ids in xip[] */
/* 活跃子事务 */
TransactionId *subxip;
int32 subxcnt; /* # of xact ids in subxip[] */
bool suboverflowed; /* has the subxip array overflowed? */
bool takenDuringRecovery; /* recovery-shaped snapshot? */
bool copied; /* false if it's a static snapshot */
/* 事务中的command id 一个语句加1 */
CommandId curcid; /* in my xact, CID < curcid are visible */
/*
* An extra return value for HeapTupleSatisfiesDirty, not used in MVCC
* snapshots.
*/
uint32 speculativeToken;
/*
* Book-keeping information, used by the snapshot manager
*/
uint32 active_count; /* refcount on ActiveSnapshot stack */
uint32 regd_count; /* refcount on RegisteredSnapshots */
pairingheap_node ph_node; /* link in the RegisteredSnapshots heap */
/* 快照生成的时间 */
TimestampTz whenTaken; /* timestamp when snapshot was taken */
/* 快照生成时的lsn */
XLogRecPtr lsn; /* position in the WAL stream when taken */
} SnapshotData;
xmin、xmax、xip记录了整个系统事务状态。
- xmin:当前所有活跃事务的最小事务ID, 如果有一个事务小于这个ID,说明这个事务已经提交或终止了。
- xmax:当前活跃的最大事务,这个值从latestCompletedXid拿到的,
xmax=latestCompletedXid 1
;当事务提交时,如果事务ID比latestCompletedXid大,需要更新latestCompletedXid为当前事务ID。也就是说大于等于xmax的事务一定是活跃的。
GetSnapshotData(Snapshot snapshot)
{
...
/* xmax is always latestCompletedXid 1 */
xmax = ShmemVariableCache->latestCompletedXid;
...
}
小于xmin的一定结束了,大于xmax的一定是活跃的,那中间的事务需要查看xip活跃事务列表。不在xip中的事务一定已经结束了。
2 快照处理函数
有了上述信息就可以判断元组的可见性了,判断会用到一批函数记录在SnapshotData->satisfies
中(PG10)
PG13更新了函数指向方式:
代码语言:javascript复制typedef struct SnapshotData
{
SnapshotType snapshot_type; /* type of snapshot */
...
每种类型对应不同的处理函数
- SNAPSHOT_MVCC
- 使用快照判断MVCC可见、本事务写入可见
- SNAPSHOT_SELF
- 如果元组记录的事务已经提交,可见
- 如果元组记录的事务正在运行,且是当前事务,可见
- SNAPSHOT_ANY
- 总是可见
- SNAPSHOT_TOAST
- toast可见性依赖表上元组
- SNAPSHOT_DIRTY
- 如果元组是当前事务写入的 或 写入的事务已经提交或终止,则可见性和SELF一致
- 如果写入的事务还在运行,和SELF不同,收集当前元组的版本信息保存到快照中
- SNAPSHOT_HISTORIC_MVCC
- 逻辑复制中逻辑解码的可见性判断
- SNAPSHOT_NON_VACUUMABLE
- false表示元组已经对所有人不可见
3 快照获取
生成快照需要遍历PGPROC和PGXACT结构,查询正在运行的所有事务信息。
- 对于RC级别的事务,每次操作都需要重新获取快照。
- 对于RR、S级别的事务,只使用第一次获取的快照。
快照获取:GetTransactionSnapshot 快照生成:GetSnapshotData
代码语言:javascript复制Snapshot
GetTransactionSnapshot(void)
{
// 逻辑解码直接拿HistoricSnapshot
if (HistoricSnapshotActive())
{
Assert(!FirstSnapshotSet);
return HistoricSnapshot;
}
// 第一次的事务拿快照会进这个分支,FirstSnapshotSet初始化=false
if (!FirstSnapshotSet)
{
// catalog的快照不能比事务快照旧,失效掉后面重新拿
InvalidateCatalogSnapshot();
...
// 如果隔离级别>=XACT_REPEATABLE_READ:可重复读或可串行化
if (IsolationUsesXactSnapshot())
{
// 初始化可串行化的控制结构
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);
}
// 如果隔离级别是RC,直接获取一个当前快照
else
CurrentSnapshot = GetSnapshotData(&CurrentSnapshotData);
// 下次进来不走这个分支
FirstSnapshotSet = true;
return CurrentSnapshot;
}
// 不是第一次调用了、而且隔离界别>=RR
if (IsolationUsesXactSnapshot())
return CurrentSnapshot;
/* Don't allow catalog snapshot to be older than xact snapshot. */
InvalidateCatalogSnapshot();
// RC级别、不是第一次拿快照:重新拿快照
CurrentSnapshot = GetSnapshotData(&CurrentSnapshotData);
return CurrentSnapshot;
}