相关: 《Postgresql源码(18)PGPROC相关结构》 《Postgresql源码(65)新快照体系Globalvis工作原理分析》 《Postgresql快照优化Globalvis新体系分析(性能大幅增强)》 《Improving Postgres Connection Scalability: Snapshots》 一些历史悠久的分析: 《Postgresql源码(28)获取快照GetSnapshotData流程分析和性能问题》
先介绍下新版本PG删除了PGXACT结构后,事务ID是怎么存的:
debug命令备忘(假设numProcs=3) p *procArray p allProcs[procArray->pgprocnos0] p allProcs[procArray->pgprocnos1] p allProcs[procArray->pgprocnos2] p ProcGlobal->xids0 p ProcGlobal->xids1 p ProcGlobal->xids2
总结:
ProcGlobal->xids
数组,拿到所有的xid(优化一:紧凑数组)。PGPROC->xmin
(还是要更新自己的xmin,这是必须的,无优化)代码流程:
GetSnapshotData
...
LWLockAcquire(ProcArrayLock, LW_SHARED)
...
for (int pgxactoff = 0; pgxactoff < numProcs; pgxactoff++)
TransactionId xid = UINT32_ACCESS_ONCE(other_xids[pgxactoff]);
/* 如果xid>=xmax可以直接跳过,因为这个xid是在我拿快照后启动的,对我来说肯定不可见 */
if (!NormalTransactionIdPrecedes(xid, xmax))
continue;
/* 如果xid < xmin,xid也要包含在xmin中,xmin表示最小的活跃事务 */
if (NormalTransactionIdPrecedes(xid, xmin))
xmin = xid;
/* 记录到快照中 */
xip[count++] = xid;
/* 更新一次xmin */
if (!TransactionIdIsValid(MyProc->xmin))
MyProc->xmin = TransactionXmin = xmin;
LWLockRelease(ProcArrayLock);
这部分要对比历史代码来看。
GetSnapshotData
//【0】前面把快照已经计算完了。下面“顺便”计算下全局最小可清理位点:RecentGlobalXmin
//【1】拿到最小事务ID
if (TransactionIdPrecedes(xmin, globalxmin))
globalxmin = xmin;
//【2】最小可清理位点传给RecentGlobalXmin,如果配了vacuum_defer_cleanup_age
// 把最小可清理位点,再往前推,清理时会保留更多的事务,给高延迟的备机使用
RecentGlobalXmin = globalxmin - vacuum_defer_cleanup_age;
if (!TransactionIdIsNormal(RecentGlobalXmin))
RecentGlobalXmin = FirstNormalTransactionId;
//【3】如果复制槽指定了某个位点,vacuum是不能清理的;
if (TransactionIdIsValid(replication_slot_xmin) &&
NormalTransactionIdPrecedes(replication_slot_xmin, RecentGlobalXmin))
RecentGlobalXmin = replication_slot_xmin;
...
...
测试场景:
事务一RC-----------4000440启动-------------------4000440结束------------------------------->
事务二RR----------------------4000450启动-----------------------------4000450结束---------->
事务三RC--------------------------------------------调试位置------------------------------->
调试位置:
GetSnapshotData
//【0】前面把快照已经计算完了。下面“顺便”计算下全局最小可清理位点,但是由于没有遍历PGPROC的xmin,只有自己遍历出来的xid,
/* maintain state for GlobalVis* */
{
TransactionId def_vis_xid;
TransactionId def_vis_xid_data;
FullTransactionId def_vis_fxid;
FullTransactionId def_vis_fxid_data;
FullTransactionId oldestfxid;
//【1】oldestfxid = 726
oldestfxid = FullXidRelativeTo(latest_completed, oldestxid);
//【2】def_vis_xid_data = 4000450
def_vis_xid_data =
TransactionIdRetreatedBy(xmin, vacuum_defer_cleanup_age);
//【3】def_vis_xid_data = 4000450
def_vis_xid_data =
TransactionIdOlder(def_vis_xid_data, replication_slot_xmin);
//【4】def_vis_xid = 4000450
def_vis_xid = def_vis_xid_data;
def_vis_xid = TransactionIdOlder(replication_slot_catalog_xmin, def_vis_xid);
def_vis_fxid = FullXidRelativeTo(latest_completed, def_vis_xid);
def_vis_fxid_data = FullXidRelativeTo(latest_completed, def_vis_xid_data);
//【5】4000440 ----> 4000450
GlobalVisSharedRels.definitely_needed =
FullTransactionIdNewer(def_vis_fxid,
GlobalVisSharedRels.definitely_needed);
...
//【6】4000440 ----> 4000440
GlobalVisSharedRels.maybe_needed =
FullTransactionIdNewer(GlobalVisSharedRels.maybe_needed,
oldestfxid);
...
}
总结:
不活跃 < maybe_needed < ... < definitely_needed <= 活跃
,那maybe_needed和definitely_needed中间的值怎么知道有没有提交?看下面函数:
bool
GlobalVisTestIsRemovableFullXid(GlobalVisState *state,
FullTransactionId fxid)
{
// 比maybe_needed小的肯定已经不活跃了,可以清理!
if (FullTransactionIdPrecedes(fxid, state->maybe_needed))
return true;
// 比definitely_needed大于或等于,肯定活跃的,不能清理!
if (FullTransactionIdFollowsOrEquals(fxid, state->definitely_needed))
return false;
// 中间的怎么办?调用GlobalVisUpdate
if (GlobalVisTestShouldUpdate(state))
{
// 调用ComputeXidHorizons计算每一个PROC的xmin,然后更新到GlobalVis变量中。
GlobalVisUpdate();
Assert(FullTransactionIdPrecedes(fxid, state->definitely_needed));
return FullTransactionIdPrecedes(fxid, state->maybe_needed);
}
else
return false;
}
PGXACT->xmin
会有什么影响?