rc事务快照获取的一次实例和总结。
--------活跃--------提交--------活跃--------回滚--------活跃--------提交
事务ID:1840251 1840252 1840253 1840254 1840255 1840256
五个事务ID执行内容:
1840251:begin;insert into a values (199);
1840252:begin;insert into a values (199);commit;
1840253:begin;insert into a values (199);
1840254:begin;insert into a values (200);rollback;
1840255:begin;insert into a values (199);
1840256:begin;insert into a values (199);commit;
五个活跃进程pid
1840251:18004:活跃事务一
1840253:18257:活跃事务二
1840255:18523:活跃事务三
1840256:19656:已提交事务一
1840257:24492:调试进程
【调试进程】执行begin;insert into a values (199) 获取快照,观察快照获取过程
begin;insert into a values (199);
在调试进程执行insert into a values (199);时会获取快照:
Snapshot
GetSnapshotData(Snapshot snapshot)
{
ProcArrayStruct *arrayP = procArray;
TransactionId xmin;
TransactionId xmax;
TransactionId globalxmin;
int index;
int count = 0;
int subcount = 0;
bool suboverflowed = false;
volatile TransactionId replication_slot_xmin = InvalidTransactionId;
volatile TransactionId replication_slot_catalog_xmin = InvalidTransactionId;
...
LWLockAcquire(ProcArrayLock, LW_SHARED);
上一个使用完的XID,按当前的事务状态:xid=1840256
xmax = ShmemVariableCache->latestCompletedXid;
TransactionIdAdvance(xmax);
xmax++:xmax=1840257
globalxmin初始化为1840257
globalxmin = xmin = xmax;
...
if (!snapshot->takenDuringRecovery)
{
int *pgprocnos = arrayP->pgprocnos;
int numProcs;
当前进程状态?
pgprocnos | pid | 进程名 | xid | xmin |
---|---|---|---|---|
94 | 24492 | 当前调试进程 | 1840257 | 0 |
95 | 19656 | 已提交事务一 | 0 | 0 |
96 | 18523 | 活跃事务三 | 1840255 | 0 |
97 | 18257 | 活跃事务二 | 1840253 | 0 |
98 | 2301 | wal sender | 0 | 0 |
99 | 18004 | 活跃事务一 | 1840251 | 0 |
111 | 2290 | bgworker: logical replication launcher | 0 | 0 |
当前
xmin | xmax | globalxmin |
---|---|---|
1840257 | 1840257 | 1840257 |
/*
* 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.
*/
numProcs = arrayP->numProcs;
for (index = 0; index < numProcs; index++)
{
int pgprocno = pgprocnos[index];
volatile PGXACT *pgxact = &allPgXact[pgprocno];
TransactionId xid;
...
/* Update globalxmin to be the smallest valid xmin */
xid = pgxact->xmin; /* fetch just once */
如果xmin是有效的,记录最大的xmin到globalxmin中
if (TransactionIdIsNormal(xid) &&
NormalTransactionIdPrecedes(xid, globalxmin))
globalxmin = xid;
/* Fetch xid just once - see GetNewTransactionId */
xid = pgxact->xid;
/*
* If the transaction has no XID assigned, we can skip it; it
* won't have sub-XIDs either. If the XID is >= xmax, we can also
* skip it; such transactions will be treated as running anyway
* (and any sub-XIDs will also be >= xmax).
*/
xid = 1840257的第一轮循环会直接continue,为什么?xmax 现在也等于1840257,所以无论如何1840257都会算做运行中的事务。
这里两个条件无需继续遍历allxact:
if (!TransactionIdIsNormal(xid)
|| !NormalTransactionIdPrecedes(xid, xmax))
continue;
/*
* We don't include our own XIDs (if any) in the snapshot, but we
* must include them in xmin.
*/
如果xid < xmin,需要记录最小的活跃事务ID,能查出来xid!=0就说明事务正在运行中。
xip中不记录当前进程的xid
if (NormalTransactionIdPrecedes(xid, xmin))
xmin = xid;
if (pgxact == MyPgXact)
continue;
/* Add XID to snapshot. */
snapshot->xip[count++] = xid;
记录子事务ID信息,子事务相关可以看这篇:
if (!suboverflowed)
{
if (pgxact->overflowed)
suboverflowed = true;
else
{
int nxids = pgxact->nxids;
if (nxids > 0)
{
volatile PGPROC *proc = &allProcs[pgprocno];
memcpy(snapshot->subxip + subcount,
(void *) proc->subxids.xids,
nxids * sizeof(TransactionId));
subcount += nxids;
}
}
}
}
}
else
{
...
// in hot standby
}
循环总结:
循环结束时
xmin | xmax | globalxmin |
---|---|---|
1840251 | 1840257 | 1840257 |
...
(重要)
把全局最小的xmin更新到共享内存MyPgXact->xmin 和 TransactionXmin 和 globalxmin中。
TransactionIdPrecedes is <
if (!TransactionIdIsValid(MyPgXact->xmin))
MyPgXact->xmin = TransactionXmin = xmin;
LWLockRelease(ProcArrayLock);
if (TransactionIdPrecedes(xmin, globalxmin))
globalxmin = xmin;
...
RecentXmin = xmin;
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;
...
return snapshot;
}
gdb
(gdb) p *snapshot
$34 = {satisfies = 0xa2f5bb <HeapTupleSatisfiesMVCC>, xmin = 1840251, xmax = 1840257, xip = 0x12c81c0, xcnt = 3,
subxip = 0x7fecacadc010, subxcnt = 0, suboverflowed = 0 '\000', takenDuringRecovery = 0 '\000', copied = 0 '\000', curcid = 1,
speculativeToken = 0, active_count = 0, regd_count = 0, ph_node = {first_child = 0x0, next_sibling = 0x0, prev_or_parent = 0x0},
whenTaken = 0, lsn = 0}
(不关注vacuum相关逻辑)
- 第一步:ProcArrayLock加读锁,在锁内通过xmax = ShmemVariableCache->latestCompletedXid拿到上一个使用过的xid,xmax=xid+1
- 第二步:遍历所有活跃进程,最小的xid记录到xmin中,最小的xmin记录到globalxmin中。
- 第三步:如果MyXact中的xmin无效,更新MyPgXact->xmin = TransactionXmin = xmin。(造成cache line invalid,性能问题点,针对这点PG13有大幅度优化)
共享内存中的MyPgXact->xmin被频繁更新,导致cpu cache失效。
事务内的一次查询就会造成四次共享内存更新
exec_simple_query
GetSnapshotData
MyPgXact->xmin = 0 --> 1840251
exec_simple_query
PopActiveSnapshot
SnapshotResetXmin
MyPgXact->xmin = 1840251 --> 0
exec_simple_query
PortalRun
PortalRunMulti
GetTransactionSnapshot
GetSnapshotData
MyPgXact->xmin = 0 --> 1840251
exec_simple_query
PortalRun
PortalRunMulti
PopActiveSnapshot
SnapshotResetXmin
MyPgXact->xmin = 1840251 --> 0
后面准备专门对这部分做一次性能分析。