熟悉Greenplum数据库的朋友应该都知道,GP底层是使用PostgreSQL数据库来实行MPP架构的,而对于事务控制这一块,也是使用PostgreSQL的多版本控制MVCC,实现了读写分离,显然就会提高数据库每秒查询的性能。
在Read Commit事务隔离级别时,查询请求只读取查询请求之前已经提交的事务的数据更改,对当前版本的数据并不影响;
而DML语句,会操作当前版本。因此做到了读写分离的目的,提高数据库并发能力。
我们先来回顾一下PostgreSQL里面的MVCC多版本控制。
在PostgreSQL中,每一个事务都会得到一个被称作为 XID 的事务ID。这里说的事务不仅仅是被 BEGIN - COMMIT 包裹的一组语句,还包括单条的insert、update或者delete语句。当一个事务开始时,PostgreSQL递增XID,然后把它赋给这个事务。PostgreSQL还在系统里的每一行记录上都存储了事务相关的信息,这被用来判断某一行记录对于当前事务是否可见。举个例子,当你插入一行记录时,PostgreSQL会把当前事务的XID存储在这一行中并称之为 xmin。只有那些已提交的而且xmin比当前事务的XID小的记录对当前事务才是可见的。这意味着,你可以开始一个新事务然后插入一行记录,直到你提交( COMMIT )之前,你插入的这行记录对其他事务永远都是不可见的。等到提交以后,其他后创建的新事务就可以看到这行新记录了,因为他们满足了 xmin < XID 条件,而且创建那一行记录的事务也已经完成。
对于 DELETE 和 UPDATE 来说,机制也是类似的,但不同的是对于它们PostgreSQL使用叫做 xmax 的值来判断数据的可见性。这幅图展示了在两个并发的插入/读取数据的事务中,MVCC在事务隔离方面是怎么起作用的。
PostgreSQL使用xmin,xmax,cmin,cmax等标记来实现多版本,他们的含义为:
xmin:在创建记录(tuple)时,记录此时的事务id,后面每次update也会更新。
xmax: 在更新或删除tuple或者lock时,记录此时的事务id;如果记录没有被删除,那么此时为0。
cmin:插入该元组的命令在插入事务中的命令标识(从0开始累加)
cmax:删除该元组的命令在插入事务中的命令标识(从0开始累加)
但是对于Greenplum数据库来说,它毕竟是基于多个postgres实例来实现MPP架构的数据库,所以上面的标记的值可能与单个postgres有区别。下面我们示例中会说明。
#装载数据,非并行,如果并行加载数据的话,可以考虑使用gpfdist或gpload等方式
zhangyun_db=# COPY test_mvcc from ‘/home/gpadmin/mvcc.txt‘ with delimiter as ‘|‘ null as ‘‘;
COPY 4
zhangyun_db=# select * from test_mvcc ;
id | name
----+-----------
4 | Hadoop
3 | Greenplum
2 | Hive
1 | Spark
(4 rows)
zhangyun_db=# select t.*, t.xmin, t.xmax, t.cmin, t.cmax from test_mvcc t;
id | name | xmin | xmax | cmin | cmax
----+------------+--------+------+------+------
8 | Flink | 449908 | 0 | 0 | 0
4 | Hadoop | 449906 | 0 | 0 | 0
5 | HBase | 449775 | 0 | 0 | 0
7 | PostgreSQL | 457913 | 0 | 0 | 0
2 | Hive | 449910 | 0 | 0 | 0
3 | Greenplum | 449909 | 0 | 0 | 0
6 | HAWQ | 449899 | 0 | 0 | 0
1 | Spark | 449905 | 0 | 0 | 0
(8 rows)
从上图可以看出,8条记录的xmin是不一样的(如果是PostgreSQL数据库的话,这里应该是一样的,因为这些数据是通过同一个事务copy创建的)。
另外xmax都为0,说明数据自从导入后就没有被删除。
下面我们来演示在Greenplum数据中执行update的情况:
请打开两个linux终端A和B,方便数据比对和查看。
首先在终端A执行,但不提交:
zhangyun_db=# begin;
BEGIN
zhangyun_db=# update test_mvcc set name = ‘Hive On Spark‘ where id = 2;
UPDATE 1
终端B查看:
zhangyun_db=# select t.*, t.xmin, t.xmax, t.cmin, t.cmax from test_mvcc t;
id | name | xmin | xmax | cmin | cmax
----+------------+--------+--------+------+------
4 | Hadoop | 449906 | 0 | 0 | 0
7 | PostgreSQL | 457913 | 0 | 0 | 0
6 | HAWQ | 449899 | 0 | 0 | 0
2 | Hive | 449910 | 450412 | 0 | 0
1 | Spark | 449905 | 0 | 0 | 0
8 | Flink | 449908 | 0 | 0 | 0
5 | HBase | 449775 | 0 | 0 | 0
3 | Greenplum | 449909 | 0 | 0 | 0
(8 rows)
可以看到,对于id为2的数据行的xmax发生了变化,但是数据本身是没有变化的,因为终端A的事务还没有提交。
接着,我们在终端A执行提交动作,如下:
zhangyun_db=# commit;
COMMIT
同时在终端B再查看:
zhangyun_db=# select t.*, t.xmin, t.xmax, t.cmin, t.cmax from test_mvcc t;
id | name | xmin | xmax | cmin | cmax
----+---------------+--------+------+------+------
2 | Hive On Spark | 450412 | 0 | 0 | 0
6 | HAWQ | 449899 | 0 | 0 | 0
5 | HBase | 449775 | 0 | 0 | 0
7 | PostgreSQL | 457913 | 0 | 0 | 0
4 | Hadoop | 449906 | 0 | 0 | 0
3 | Greenplum | 449909 | 0 | 0 | 0
8 | Flink | 449908 | 0 | 0 | 0
1 | Spark | 449905 | 0 | 0 | 0
(8 rows)
可以看到id为2的记录,其xmin已经变化了。
根据上面的结果,不知道大家有没有发现,对于Greenplum来说,更新或者删除都没有修改cmin和cmax的值。
在PostgreSQL中,cmin和cmax用于判断同一个事务内的其他命令导致的行版本变更是否可见。如果一个事务内的所有命令严格顺序执行,那么每个命令总能看到之前该事务内的所有变更,不需要使用命令标识。然而一个事务内存在命令交替执行的情况,比如使用游标进行查询。Fetch游标时看到的是声明游标时的数据快照而不是Fetch执行时,即声明游标后对数据的变更对该游标不可见。
这一块的内容,后续抽时间分析源码再写一篇文章进行分析。