openGauss源码解析(76)
openGauss源码解析:事务机制源码解析(7)
2. 多版本快照机制
因为openGauss使用一段共享内存来实现快照的获取以及各线程事务信息的管理,计算快照持有共享锁以及事务结束持有排他锁有严重的锁争抢问题。为了解决该冲突,openGauss引入了多版本快照机制解决锁冲突。每当事务结束时,持有排他锁、计算快照的一个版本,记录到一个环形缓冲区队列内存里;当别的线程获取快照时,并不持有共享锁去重新计算,而是通过原子操作到该环形队列顶端获取最新快照并将其引用计数加1;待拷贝完了快照信息后,将引用计数减1;当槽位引用计数为0时,表示可以被新的快照复用。
1) 多版本快照数据结构
多版本快照数据结构代码如下:
typedef struct _snapxid {
TransactionId xmin;
TransactionId xmax;
CommitSeqNo snapshotcsn;
TransactionId localxmin;
bool takenDuringRecovery;
ref_cnt_t ref_cnt[NREFCNT]; /* 该快照的引用计数,如果为0则可复用 */
} snapxid_t; /*多版本快照内容,在openGauss CSN方案下,仅需要记录xmin、xmax、snapshotcsn等关键信息即可。*/
static snapxid_t* g_snap_buffer = NULL; /* 缓冲区队列内存区指针 */
static snapxid_t* g_snap_buffer_copy = NULL; /* 缓冲区队列内存的浅拷贝 */
static size_t g_bufsz = 0;
static bool g_snap_assigned = false; /*多版本快照buffer队列是否已初始化 */
#define SNAP_SZ sizeof(snapxid_t) /* 每一个多版本快照的size大小 */
#define MaxNumSnapVersion 64 /* 多版本快照队列的大小,64个版本 */
static volatile snapxid_t* g_snap_current = NULL; /* 当前的快照指针 */
static volatile snapxid_t* g_snap_next = NULL; /* 下一个可用槽位的快照指针 */
2) buffer队列创建流程
在创建共享内存时,根据MaxNumSnapVersion函数的size生成“MaxNumSnapVersion * SNAP_SZ”大小的共享内存区。并将g_snap_current置为0号偏移,g_snap_next置为“1 * SNAP_SZ”偏移。
3) 多版本快照的计算
(1) 获取当前g_snap_next。
(2) 保证当前已持有Proc数组的排他锁,进行xmin、xmax、CSN等关键结构的计算,并存放到g_snap_next中。
(3) 寻找下一个refcount为0可复用的槽位,将g_snap_current赋值为g_snap_next,g_snap_next赋值为可复用的槽位偏移。
4) 多版本快照的获取
(1) 获取g_snap_current指针并将当前快照槽位的引用计数加1,防止并发更新快照时被复用。
(2) 将当前快中的信息拷贝到当前连接的静态快照内存中。
(3) 释放当前多版本快照,并将当前快照槽位的引用计数减1。
5) 关键函数
(1) CreateSharedRingBuffer:创建多版本快照共享内存信息。
(2) GetNextSnapXid:获取下一个多版本快照位置。函数代码如下:
static inline snapxid_t* GetNextSnapXid()
{
return g_snap_buffer ? (snapxid_t*)g_snap_next : NULL;
}
(3) SetNextSnapXid:获取下一个可用的槽位,并且将当前多版本快照最新更新。函数代码如下:
static void SetNextSnapXid()
{
if (g_snap_buffer != NULL) {
g_snap_current = g_snap_next; /* 将最新的多版本快照更新到最新。*/
pg_write_barrier(); /* 此处是防止buffer ring初始化时的ARM乱序问题。*/
g_snap_assigned = true;
snapxid_t* ret = (snapxid_t*)g_snap_current;
size_t idx = SNAPXID_INDEX(ret);
loop: /* 主循环,整体思路是不停遍历多版本槽位信息,一直找到一个refcout为0的可重用槽位。*/
do {
++idx;
/* 如果发生回卷,那么重头再找 */
if (idx == g_bufsz)
idx = 0;
ret = SNAPXID_AT(idx);
if (IsZeroRefCount(ret)) {
g_snap_next = ret;
return;
}
} while (ret != g_snap_next);
ereport(WARNING, (errmsg("snapshot ring buffer overflow.")));
/* 当前多版本快照个数为64个,理论上可能是会出现槽位被占满,如果没有空闲槽位,重新遍历即可。 */
goto loop;
}
}
(4) CalculateLocalLatestSnapshot:计算多版本快照信息。函数代码如下:
void CalculateLocalLatestSnapshot(bool forceCalc)
{
…/* 初始化变量 */
snapxid_t* snapxid = GetNextSnapXid(); /*设置下一个空闲多版本快照槽位信息 */
/* 初始化xmax为 latestCompletedXid + 1 */
xmax = t_thrd.xact_cxt.ShmemVariableCache->latestCompletedXid;
TransactionIdAdvance(xmax);
/*并不是每个事务提交都会重新计算xmin和oldestxmin,只有每1000个事务或者每隔1s才会计算,此时xmin及oldestxmin一般偏小,但是不影响可见性判断。 */
currentTimeStamp = GetCurrentTimestamp();
if (forceCalc || ((++snapshotPendingCnt == MAX_PENDING_SNAPSHOT_CNT) ||
(TimestampDifferenceExceeds(snapshotTimeStamp, currentTimeStamp, CALC_SNAPSHOT_TIMEOUT)))) {
snapshotPendingCnt = 0;
snapshotTimeStamp = currentTimeStamp;
/* 初始化xmin */
globalxmin = xmin = xmax;
int* pgprocnos = arrayP->pgprocnos;
int numProcs;
/*
循环遍历proc并计算快照相应值
*/
numProcs = arrayP->numProcs;
/*主要流程,遍历proc_base_all_xacts,将其中pgxact->xid的最小值记为xmin,其中pgxact->xmin的最小值记为oldestxmin。 */
for (index = 0; index < numProcs; index++) {
int pgprocno = pgprocnos[index];
volatile PGXACT* pgxact = &g_instance.proc_base_all_xacts[pgprocno];
TransactionId xid;
if (pgxact->vacuumFlags & PROC_IN_LOGICAL_DECODING)
continue;
/* 对于autovacuum的xmin,跳过,避免长VACUUM阻塞脏元组回收 */
if (pgxact->vacuumFlags & PROC_IN_VACUUM)
continue;
/* 用最小的xmin来更新globalxmin */
xid = pgxact->xmin;
if (TransactionIdIsNormal(xid) && TransactionIdPrecedes(xid, globalxmin))
globalxmin = xid;
xid = pgxact->xid;
if (!TransactionIdIsNormal(xid))
xid = pgxact->next_xid;
if (!TransactionIdIsNormal(xid) || !TransactionIdPrecedes(xid, xmax))
continue;
if (TransactionIdPrecedes(xid, xmin))
xmin = xid;
}
if (TransactionIdPrecedes(xmin, globalxmin))
globalxmin = xmin;
t_thrd.xact_cxt.ShmemVariableCache->xmin = xmin;
t_thrd.xact_cxt.ShmemVariableCache->recentLocalXmin = globalxmin;
}
/* 此处给多版本快照信息赋值,xmin、oldestxmin因为不是及时计算故可能偏小,xmax、CSN号都是当前的准确值,注意计算快照的时候必须持有排他锁。 */
snapxid->xmin = t_thrd.xact_cxt.ShmemVariableCache->xmin;
snapxid->xmax = xmax;
snapxid->localxmin = t_thrd.xact_cxt.ShmemVariableCache->recentLocalXmin;
snapxid->snapshotcsn = t_thrd.xact_cxt.ShmemVariableCache->nextCommitSeqNo;
snapxid->takenDuringRecovery = RecoveryInProgress();
SetNextSnapXid(); /*设置当前多版本快照 */
}
(5) GetLocalSnapshotData:获取最新的多版本快照供事务使用。函数代码如下:
Snapshot GetLocalSnapshotData(Snapshot snapshot)
{
/* 检查是否有多版本快照。在recover启动之前,是没有计算出多版本快照的,此时直接返回。 */
if (!g_snap_assigned || (g_snap_buffer == NULL)) {
ereport(DEBUG1, (errmsg("Falling back to origin GetSnapshotData: not assigned yet or during shutdown\n")));
return NULL;
}
pg_read_barrier(); /*为了防止ringBuffer初始化时的ARM乱序问题*/
snapxid_t* snapxid = GetCurrentSnapXid(); /* 将当前的多版本快照refcount++,避免被并发计算新快照的事务重用。 */
snapshot->user_data = snapxid;
… /* 此处将多版本快照snapxid中的信息赋值给快照,注意此处是深拷贝,因为多版本快照仅有几个变量的关键信息,直接赋值即可,之后就可以将相应的多版本快照refcount释放。 */
u_sess->utils_cxt.RecentXmin = snapxid->xmin;
snapshot->xmin = snapxid->xmin;
snapshot->xmax = snapxid->xmax;
snapshot->snapshotcsn = snapxid->snapshotcsn;
…
ReleaseSnapshotData(snapshot); /* 将多版本快照的refcount释放,以便可以被重用。 */
return snapshot;
}