我们知道Heap表的事务隔离是通过MVCC实现,但是在列存表没有记录xmin,xmax等多版本信息,仅仅记录了块的元信息以及数据,那么它是如何实现事务隔离的?仍然借助于Heap表,每创建一个列存表,同时创建一个heap辅助表表,通过select * from pg_appendonly可以看到辅助表的OID(segrelid),这个辅助表几面记录了什么呢?
typedef struct AOCSVPInfoEntry { int64 eof; int64 eof_uncompressed; } AOCSVPInfoEntry;从这个结构体可以看出来,辅助表记录了当前事务在列存文件中可见的结尾偏移量。如果其他连接插入了数据,列存文件变大了,但是当前事务还是只能看到插入之前的结尾偏移量,多余的数据是看不到的。
如图所示,B事务新增的数据的EOF位置存在Heap表中,而Heap表满足MVCC的,所以A事务看不到B事务中Heap表的变更,继续使用A事务开始的是EOF。
总之,列存的事务原理是靠辅助Heap表实现的。
机器可能宕机、断电等,事务需要回滚,数据可能部分写,对于这些场景Greenplum列存是如何处理的呢?
在Greenplum列存表原理里面我们已经提到,并发导入是靠多个文件实现,也就是说每个Insert连接只会负责一个列文件,假设事务开始的时候文件EOF为EOF_start, 导入后变成EOF_end,如果事务回滚,辅助Heap表里面记录的EOF回滚到事务开始的EOF_start,回滚完成。下次事务导入将会从EOF_start继续增加数据,覆盖EOF_start--EOF_end之间的数据。
Greenplum列存表是不写XLOG的,那么它是如何做到崩溃之后恢复的呢?
每次commit提交强制做fsync,所以只要事务提交,那么磁盘数据就是一致的,崩溃恢复不影响。新的事务导入数据,强制新增block,不能使用上一个事务的block,即便上一个事务的block还没有写满。这样就不会存在复用上一个事务block写数据时写到一半出问题丢失上个事务block的数据。因此强烈不建议使用每次insert一条方式导入数据,这样会产生的大量的block,每条记录都对一个block,影响扫描性能。