用法
create table testao(id int, name text) with (APPENDONLY=true, ORIENTATION=column, COMPRESSTYPE=zlib, COMPRESSLEVEL=5, BLOCKSIZE=1048576, OIDS=false)
APPENDONLY=true, ORIENTATION=column这两个属性决定了这是列存压缩表。
COMPRESSTYPE: 压缩方式,支持zlip,RTE等
COMPRESSLEVEL: 压缩级别,0-9
BLOCKSIZE: 块大小8kB-2MB
优势
- 节约成本,列存压缩表空间占用远小于普通的heap表空间
- 对查询涉及的列很少时候,无需去读其他列的数据,减少IO
- 追加写速度快
存储结构
总体方法是一个文件只存一个列的值,一个文件由多个block组成。
- 组成结构
每个列占用一个至多个文件,最多128个,不同列的值不会同时出现一个文件。之所以这么设计,是为了解决并发问题。
- block结构
struct getBlockInfo
{
int32 contentLen;
int execBlockKind;
int64 firstRow; /* is expected to be -1 for pre-4.0 blocks */
int rowCnt;
bool isLarge;
bool isCompressed;
} getBlockInfo;
BLOCKSIZE大小是8kB-2MB,由压缩前的数据大小决定,压缩后的block大小不一,其中block header不参与压缩。如果插入的值超过blocksize,那么数据将会拆成多个block;对于NULL值,block中使用bit位表示。
- RowNum机制
对于heap表,由tupleid决定每条记录的位置(tupleid包含记录在文件中偏移位置),而对于AO表,数据是压缩的,没法确定value在文件中偏移位置,因此引用了rownum,每条记录都有自己的rownum,rownum一直增长,rownum可能不连续,但是没关系,我们的block中记录了起始rownum以及block在文件中偏移位置,所以只要给我们一个rownum,我们就能定位到它所在的block,然后从block中就可以遍历到这个rownum对应的记录。
每次insert都会更新rownum,为了解决频繁更新问题,每次申请100个rownum,批量insert时每100个才会更新rownum。通过select * from gp_fastsequence
可以查看已使用的rownum。
并发insert原理
当多个Session同时向表中insert数据时,如果只有一个文件,那么每次追加都需要对文件加锁,显然会降低插入性能,为了解决这个问题,当有并发insert时,每个session向各自对应的文件insert数据。不过最大的并发是128,单个列不能超过128个文件限制。
update/delete实现
delete和update不会在数据文件中直接更新或者删除,而是使用一张heap辅助表visimap,包含一个bitmap列,通过select * from pg_appendonly
可以查看到,对任何一个数据的删除直接在bitmap中标记一下即可。update是delete和insert两步组合实现。