PostgreSQL MVCC快照机制浅析

快照隔离是数据库实现并发控制的一种常用技术.本节简单探讨了快照理论模型、PostgreSQL中的工程实践以及由此推广到Postgres-XL的可能改进思路等.

一、快照模型

快照,顾名思义表示某个时间点的状态.在日常生活中,我们使用相机拍摄,拍下来的画面可以理解为拍摄对象的快照;与此类似,连接数据库执行操作(如查询)时数据库的状态也可以视为快照.
为了方便讨论,假定数据库事务隔离级别为READ COMMITTED,考虑以下场景:
PostgreSQL MVCC快照机制浅析_第1张图片

假设在时间点T4执行操作OP,在T4获取数据库状态快照:
其中,TR1/TR4为已完结事务,TR2/TR3/TR5是正在进行中的事务,TR6是在T4后发生的事务.显然,TR1和TR4事务对数据库的修改对操作OP可见;TR6是未来发生的事务,对操作OP不可见;TR2/TR3/TR5正在进行中,同样也不可见.
就此场景而言,理论上我们只需要知道”拍摄”快照的时间T4,就可以控制数据库变化的可见性了,简单而言就是:在此时间前完结的事务,可见,否则不可见.

二、PostgreSQL工程实践

按上一小节的介绍,可以看到快照天生具有时间属性,但在PG中,事务号并没有使用时间戳等信息而是使用自然数列(1,2,…,n,…).
考虑以下场景(简单起见,假设无论是只读还是修改事务,均分配事务号):
PostgreSQL MVCC快照机制浅析_第2张图片

XID=315的事务执行操作OP,获取数据库快照,与上一个场景类型类似,TR1和TR4事务执行的数据库修改对操作OP可见;TR6是未来发生的事务,对操作OP不可见;TR2/TR3/TR5正在进行中,同样也不可见.理论上来说,我们只需要知道该查询的事务号(如315)就可以控制可见性了.但在工程实践上,PostgreSQL并没有简单的使用事务号作为快照,而是使用了最后一个已结束的事务号+1(即snapshot中的xmax)作为历史和未来的分界:
1、大于等于xmax的,属于未来事务,不可见;
2、小于xmax的,已结束事务可见,进行中事务不可见。
 出于性能的考虑,为了进一步区分小于xmax的事务,引入了xmin和xip_list:
 1)小于xmin的事务,视为已结束事务,可见;
 2)大于等于xmin小于xmax,进行中的事务,不可见,否则可见。
仍以上述场景为例(详见下图):
PostgreSQL MVCC快照机制浅析_第3张图片

最后已结束事务为200,最早仍活跃事务为125,则:
xmax = 200 + 1 = 201
xmin = 125
snapshot = 125 : 201 : 140

获取快照
通过函数txid_current_snapshot()可获取当前的快照信息:


11:05:16 (xdb@[local]:5432)testdb=# select txid_current_snapshot();
 txid_current_snapshot 
-----------------------
 2404:2404:
(1 row)
11:24:11 (xdb@[local]:5432)testdb=#

输出格式为xmin : xmax : xip_list
其中:
xmin:最早仍活跃的事务ID(以下简称XID),早于此XID的事务要么被提交并可见,要么回滚要么丢弃。
xmax:最后已完结事务(COMMITTED/ABORTED)的事务ID + 1。
xip_list:在”拍摄”快照时仍进行中的事务ID。该列表包含xmin和xmax之间的活动事务ID。
总结一下,简单来说,对于给定的XID:
XID ∈ [1,xmin),过去的事务,对此快照均可见;
XID ∈ [xmin,xmax),不考虑子事务的情况,仍处于IN_PROGRESS状态的,不可见;COMMITED状态,可见;ABORTED状态,不可见;
XID ∈ [xmax,∞),未来的事务,对此快照均不可见;

数据结构
在源码中SnapshotData结构体定义如下:


typedef struct SnapshotData
{
    //判断tuple是否可见的函数
    SnapshotSatisfiesFunc satisfies;    /* tuple test function */
    //XID ∈ [2,min)是可见的 
    TransactionId xmin;         /* all XID < xmin are visible to me */
    //XID ∈ [xmax,∞)是不可见的
    TransactionId xmax;         /* all XID >= xmax are invisible to me */
    /*
     * For normal MVCC snapshot this contains the all xact IDs that are in
     * progress, unless the snapshot was taken during recovery in which case
     * it's empty. For historic MVCC snapshots, the meaning is inverted, i.e.
     * it contains *committed* transactions between xmin and xmax.
     * 对于普通的MVCC快照,xip存储了所有正在进行中的XIDs,除非在恢复期间产生的快照(这时候数组为空)
     * 对于历史MVCC快照,意义相反,即它包含xmin和xmax之间的*已提交*事务。
     *
     * note: all ids in xip[] satisfy xmin <= xip[i] < xmax
     * 注意: 所有在xip数组中的XIDs满足xmin <= xip[i] < xmax
     */
    TransactionId *xip;
    ...
}

xmin/xmax/xip分别对应txid_current_snapshot()函数输出的xmin/xmin/xip_list.

三、Postgres-XL的改进

XL的意思是:eXtensible Lattice,可以扩展的格子,即将PostgreSQL应用在多机器上的分布式数据库。Postgres-XL 是一个完全满足ACID的、开源的、可方便进行水平扩展的、多租户安全的、基于PostgreSQL的数据库解决方案。
其体系结构如下图所示:
PostgreSQL MVCC快照机制浅析_第4张图片

Postgres-XL沿袭了PostgreSQL的事务模型,仍使用自然数列作为事务号XID,因此存在作为中心点的全局事物管理器的GTM(Global Transaction Monitor),负责处理事务ID和快照。由于GTM作为中心的存在,一是容易成为整个数据库集群的性能瓶颈,二是可能会出现中心单点错误的问题,因此GTM所负责的事务ID和快照管理是否可以通过各个节点自行处理呢?
不考虑广义相对论,在地球上,时间是绝对公平和单调”递增”的,这个属性与自然数列类似,因此我们可以考虑利用本机的时间戳作为事务号生成的输入,保证事务号按顺序的单调递增和唯一性.
时间戳的定义(来自百度百科):

时间戳是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总毫秒数。通俗的讲, 时间戳是一份能够表示一份数据在一个特定时间点已经存在的完整的可验证的数据。 它的提出主要是为用户提供一份电子证据, 以证明用户的某些数据的产生时间。 在实际应用上, 它可以使用在包括电子商务、 金融活动的各个方面, 尤其可以用来支撑公开密钥基础设施的 “不可否认” 服务。
一般时间戳使用无符号64位的整型(uint64)表示

事务ID
按Postgres-XL的体系结构,假设集群中有N个节点,每个节点都部署Coordinator协调器,可以想到的可能改进思路是在获取事务号时,由Coordinator获取本地时间戳,通过一定的机制(HLC算法/TSO服务/分布式协议…)保证该时间戳的顺序性和唯一性,然后把该时间戳通过算法(最简单的算法是对2^32进行求余数)转换为PG的事务号.

快照管理
快照管理与获取事务ID类似,在某个节点需要获取快照时,该节点的Coordinator根据时间戳获取快照中的xmax,然后通过协调其他各个节点的Coordinator获取xmin/xip_list,组成最终的snapshot.

诚然,上述思路只是理论上的推导,在工程实践仍有非常多的细节(比如各个节点之间的时间同步/如何减少网络开销等等)需要考虑.

四、参考资料

Mvcc Unmasked - Bruce Momjian
Postgres-XL
分布式系统的时间

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/6906/viewspace-2562652/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/6906/viewspace-2562652/

你可能感兴趣的:(数据库)