postgresql snapshot快照源码解析, 快照内容生成规则, 可见性是这样判断的

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;

  • 初始化
  1. xmin,xmax初始化为 初值为ShmemVariableCache->latestCompletedXid + 1

对于xmax来讲,已经完成了,latestCompletedXid 就是最新已经完成的事务ID

  1. xip, subxip 分配内存;
  • 扫描每个backend

那么知道上面backend事务信息的存储后,我们要想生成一个新的事务快照,就要去扫描每个backend信息;

  1. 比较xmin, 更新为最小的xmin;
  2. 记录每个backend xid到快照中;
  3. 记录每个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)

事务可见性判断

  1. 对于xid < xmin 的部分,肯定是可见的,也就是事务已经完结;因为xmin就是最小运行的xid;

  2. 对于 xid <= xmin , 同时 xid < xmax,在此区段的事务,就需要进行检查;

需要检查那些事务号呢? 只检查快照中记录的正在运行的事务号列表xip数组中的事务号,看它们是否完成。
对于,读已经提交,事务完成就是可见了。

  1. 对于 xid >= xmax的,都是不可见的。因为xmax是已经完成事务的最大事务号+1,所以对于当前快照来说,超过xmax的值都是未来事务,认为它们都在运行中

结尾

非常感谢大家的支持,在浏览的同时别忘了留下您宝贵的评论,如果觉得值得鼓励,请点赞,收藏,我会更加努力!

作者邮箱:[email protected]
如有错误或者疏漏欢迎指出,互相学习。

注:未经同意,不得转载!

你可能感兴趣的:(postgresql,数据库,linux,sql,c语言,database,服务器)