postgresql snapshot快照源码解读
专栏内容:postgresql内核源码分析
个人主页:我的主页
座右铭:天行健,君子以自强不息;地势坤,君子以厚德载物.
概述
本文主要介绍数据库事务快照,分别从源码实现角度和从SQL使用角度来剖析,快照的原理,作用,用途,以及在实现过程中存在的一些差异。
简介
数据库快照,可能有很多种理解,如在备份时,有快照备份,也是一种快照;
本文要介绍的快照,是数据库运行过程中,事务并发时,通过快照达到事务隔离,文中叫它事务快照,在postgresql中叫做snapshot。
通过事务快照,可以获得当前所有事务的一组状态,使得,我们在数据库的不同隔离级别下,可以事务内看到的数据可见性不一样,达到数据的隔离性。
快照源码分析
快照结构定义
typedef struct SnapshotData
{
SnapshotType snapshot_type; /* type of snapshot */
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 */
CommandId curcid; /* in my xact, CID < curcid are visible */
uint32 speculativeToken;
struct GlobalVisState *vistest;
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 */
XLogRecPtr lsn; /* position in the WAL stream when taken */
uint64 snapXactCompletionCount;
} SnapshotData;
快照内容分析
- xmin
最小正在运行的事务ID, 初值为ShmemVariableCache->latestCompletedXid + 1;
然后在所有backend中查找对应的xmin(也即每个backend对应快照的xmin),找最小值
- xmax
最大已经完成的事务ID,是ShmemVariableCache->latestCompletedXid; + 1;
- 运行事务ID列表
内容为 xip 和 xcnt
- 子事务列表
相关内容为 subxip,subxcnt,suboverflowed
- 快照版本
对应内容为 curXactCompletionCount
来自 ShmemVariableCache->xactCompletionCount
用于是否有旧快照可以快速获取,进行版本比较;
如果版本发生变化,则需要重新获取,否则就直接使用
快照生成流程
那么我们对照快照的内容,看看各成员是如何生成;
- 信息介绍
在数据库运行过程中,每个backend启动时都会创建一个PGPROC的结构,在共享内存的变量ProcGlobal中有一个数组存储;
但是backend对应的 ProcGlobal数组中的下标,存储在另一个共享内存变量 procArray 中;
在每个PGPROC的结构中保存了当前backend的快照,正在运行的事务ID;
- 初始化
- xmin,xmax初始化为 初值为ShmemVariableCache->latestCompletedXid + 1
对于xmax来讲,已经完成了,latestCompletedXid 就是最新已经完成的事务ID
- xip, subxip 分配内存;
- 扫描每个backend
那么知道上面backend事务信息的存储后,我们要想生成一个新的事务快照,就要去扫描每个backend信息;
- 比较xmin, 更新为最小的xmin;
- 记录每个backend xid到快照中;
- 记录每个backend的 subxid到快照中; 如果子事务数据空间不足,则设置 suboverflowed 标志;
子事务总是比父事务ID更新,所以这里丢失也没有关系。遍历中需要跳过的backend
- 逻辑复制 它的xmin单独管理
- lazy vacuum
- 还有当前backend也不会统计在内
快照调用
快照生成的函数接口
GetTransactionSnapshot
-> GetSnapshotData
常用的调用处
- 事务开始和结束时
/* 事务开始时,生成事务快照 */
StartTransactionCommand();
PushActiveSnapshot(GetTransactionSnapshot());
/* 事务开始时,清理事务快照 */
PopActiveSnapshot();
CommitTransactionCommand();
- 在存储过程开始时
在存储过程或者函数中也是一个完整的事务,所以事务开始,结束会生成快照
快照的优化
从生成流程看,每次生成事务快照要扫描所有的backend信息,在扫描过程是需要加ProcArrayLock共享锁的。
ProcArrayLock共享锁会阻止backend信息的变化,也就是事务的提交,代价还是相当大的。
所以在快照中增加了快照版本,当版本没有变化时,直接拿上次的快照即可。
快照的作用
生成snapshot之后,如何使用呢?
事务状态段
在snapshot中将事务状态按事务号区间分成了三部分,
由xmin, xmax组成的半闭闭开区间 [xmin, xmax)
事务可见性判断
对于xid < xmin 的部分,肯定是可见的,也就是事务已经完结;因为xmin就是最小运行的xid;
对于 xid <= xmin , 同时 xid < xmax,在此区段的事务,就需要进行检查;
需要检查那些事务号呢? 只检查快照中记录的正在运行的事务号列表xip数组中的事务号,看它们是否完成。
对于,读已经提交,事务完成就是可见了。
- 对于 xid >= xmax的,都是不可见的。因为xmax是已经完成事务的最大事务号+1,所以对于当前快照来说,超过xmax的值都是未来事务,认为它们都在运行中
结尾
非常感谢大家的支持,在浏览的同时别忘了留下您宝贵的评论,如果觉得值得鼓励,请点赞,收藏,我会更加努力!
作者邮箱:study@senllang.onaliyun.com
如有错误或者疏漏欢迎指出,互相学习。
注:未经同意,不得转载!