PostgreSQL思考—元组何时可以被删除?
预备知识
《PostgreSQL 事务—MVCC》
概述
PostgreSQL支持了MVCC,所以当执行Delete时,不会也不能彻底删除相关元组。对于Delete操作所涉及的元组,PostgreSQL只是设置其t_xmax来表示元组已经被t_xmax对应的事务删除。但此时,这条元组可能依然对某些查询事务可见,这就是元组不能直接删除的原因。那么接下来就涉及两个问题?
关于元组何时被删除,这里直接给出答案:在用户执行Vacuum时。而元组何时可以被删除则是本文重点讨论的内容。其实这个问题的核心思想很简单,回到元组不能被直接删除,是因为该元组还可能对其他事务可见。那么,只有当元组对所有事务都不可见时,元组才可以被真正删除!在《PostgreSQL 事务—MVCC》中,我们讨论过事务可见性与元组可见性,所以我们知道,删除的元组对所有事务都不可见,就意味着元组的t_xmax对所有事务都可见!
这个核心思想,说着比较简单,但是做起来却比较难,下面我们来详细阐述如何判断元组是否可以删除。
元组过期
从前面的描述中,我们知道,一条元组可以被真正删除需要具备两点要素:
那么判断一条元组是否可以被删除其实只需要在全局事务链中判断t_xmax的可见性即可。通过《PostgreSQL 事务—MVCC》我们不难看出,判断可见性是一个比较复杂的流程,所以如果按照可见性判断的流程来确定元组是否可以被删除,效率将十分低下。那么有没有什么办法可以提高效率呢?当然有。
在PostgreSQL中,存在一个全局的PGXACT数组,数组中的每一个PGXACT元组代表一个当前的用户进程。PostgreSQL在执行增、删、查、改之前都需要对当前的活跃事务链做一个快照(Snapshot)。然后将快照中最小的事务id存放在PGXACT的xmin成员中。通过《PostgreSQL 事务—MVCC》我们知道,对于当前事务而言,事务id小于xmin的事务对当前事务都可见。那么,如果我们遍历PGXACT数组,获取所有PGXACT->xmin中最小的一个xmin作为globalxmin。事务id小于globalxmin的事务即对当前所有的事务均可见。
所以如果我们在每次增、删、查、改之前获取并修改globalxmin,当我们需要判断元组是否可以被删除时,只需要判断元组的t_xmax是否小于globalxmin即可。
获取globalxmin的代码如下:
numProcs = arrayP->numProcs;
for (index = 0; index < numProcs; index++)
{
int pgprocno = pgprocnos[index];
volatile PGXACT *pgxact = &allPgXact[pgprocno];
TransactionId xid;
if (pgxact->vacuumFlags & PROC_IN_LOGICAL_DECODING)
continue;
if (pgxact->vacuumFlags & PROC_IN_VACUUM)
continue;
xid = pgxact->xmin;
if (TransactionIdIsNormal(xid) &&
NormalTransactionIdPrecedes(xid, globalxmin))
globalxmin = xid;
xid = pgxact->xid;
if (!TransactionIdIsNormal(xid)
|| !NormalTransactionIdPrecedes(xid, xmax))
continue;
if (NormalTransactionIdPrecedes(xid, xmin))
xmin = xid;
if (pgxact == MyPgXact)
continue;
snapshot->xip[count++] = xid;
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;
}
}
}
}
判断元组是否可以被删除的函数为HeapTupleSatisfiesVacuum 。
|