在介绍列存前,我们先对比一下行存与列存的区别,以便我们能更清楚的认识到他们各自适合的场景,理解什么时候应该用行存,什么情形应该用列存。
列存 OR 行存
Citus不仅支持行存,还支持列存,以适应不用的应用场景需求。行存与列存适合的应用场景不同,行存的优势在于数据写入时,一条记录命中在一个块中,IO开销相对比较小,速度快,查询多个字段时,因为记录在一个块中命中,速度快。劣势在查询少量字段时,也要访问整条记录,造成较大IO开销,同时行存压缩比相较列出小。列存按列存储,压缩比可以做到很高,查询少量字段时,所需扫描的块更少,降低了IO开销。劣势在于查询大量字段或者查询的记录数少时,要访问较多的块。简而言之就是:行存储适合非常典型的OLTP场景,而列存储适合OLAP的场景。更细化一点,什么场景最合适用列存储呢? 列存储在数据批量导入的分析场景相对行存储能够提供更好的性能,是最合适列存的场景。
Citus列存介绍
Citus Columnar基于Table Access Method Interface Definition实现,其大多数操作就与heap表基本相同。目前特性支持情况如下表:
| 特性 | 支持情况 |
|---|---|
| WAL Log | 支持 |
| ROLLBACK | 支持 |
| physical replication | 支持 |
| recovery, PITR | 支持 |
| UPDATE/DELETE | 不支持,仅支持Append-only |
| bitmap index scans | 不支持 |
| tidscans | 不支持 |
| sample scans | 不支持 |
| TOAST | 不支持 |
| ON CONFLICT语句 | 不支持 |
| tuple locks(SELECT … FOR SHARE, SELECT … FOR UPDATE) | 不支持 |
| 可串行化隔离级别 | 不支持 |
| foreign keys、unique constraints | 不支持 |
| UNLOGGED列存表 | 不支持 |
如何创建列表表呢?可在CREATE TABLE建表时使用USING columnar:
CREATE TABLE my_columnar_table
(
id INT,
i1 INT,
i2 INT8,
n NUMERIC,
t TEXT
) USING columnar;
我们查看一下我们创建的列存表,可以看到其Access method不再是heap,而是columnar。
postgres@postgres=# \d+ my_columnar_table
Table "public.my_columnar_table"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
--------+---------+-----------+----------+---------+----------+--------------+-------------
id | integer | | | | plain | |
i1 | integer | | | | plain | |
i2 | bigint | | | | plain | |
n | numeric | | | | main | |
t | text | | | | extended | |
Access method: columnar -- 列存
插入数据与查询与heap表方法相同,但是不支持UPDATE/DELETE操作。不支持UPDATE/DELETE并不影响其使用,因为列存储的最佳使用场景是数据批量导入的分析场景。
-- append only
postgres@postgres=# insert into my_columnar_table values (1,1,1,1,'one');
INSERT 0 1
postgres@postgres=# select * from my_columnar_table ;
id | i1 | i2 | n | t
----+----+----+---+-----
1 | 1 | 1 | 1 | one
(1 row)
postgres@postgres=# explain select id from my_columnar_table ;
QUERY PLAN
-----------------------------------------------------------------------------------
Custom Scan (ColumnarScan) on my_columnar_table (cost=0.00..0.00 rows=1 width=4)
Columnar Projected Columns: id
(2 rows)
-- not support update/delete
postgres@postgres=# update my_columnar_table set n = 2;
ERROR: UPDATE and CTID scans not supported for ColumnarScan
postgres@postgres=# delete from my_columnar_table ;
ERROR: UPDATE and CTID scans not supported for ColumnarScan
对于列存,我们还可以进行某些设置,比如指定列压缩算法,压缩级别等。支持设置可选项如下:
- columnar.compression : 支持none|pglz|zstd|lz4|lz4hc,默认是zstd压缩算法
- columnar.compression_level : 压缩级别,1~19
- columnar.stripe_row_limit : 指定每个stripe中行最大记录数,默认值150000。
- columnar.chunk_group_row_limit : 指定每个列数据块中最大行记录数,默认值10000。
可通过查columnar.options查看当前表的各项设置情况:
postgres@postgres=# select * from columnar.options ;
regclass | chunk_group_row_limit | stripe_row_limit | compression_level | compression
-------------------+-----------------------+------------------+-------------------+-------------
events_columnar | 10000 | 150000 | 3 | zstd
my_columnar_table | 10000 | 150000 | 3 | zstd
(2 rows)
也可以通过SHOW进行查看,SET进行设置:
postgres@postgres=# show columnar.compression;
columnar.compression
----------------------
zstd
(1 row)
-- 修改配置
postgres@postgres=# set columnar.stripe_row_limit = 10000;
SET
列存表同样支持分区表,可根据场景,对某个分区使用行存或者列存。
CREATE TABLE parent(ts timestamptz, i int, n numeric, s text)
PARTITION BY RANGE (ts);
-- columnar partition
CREATE TABLE p0 PARTITION OF parent
FOR VALUES FROM ('2020-01-01') TO ('2020-02-01')
USING COLUMNAR;
-- columnar partition
CREATE TABLE p1 PARTITION OF parent
FOR VALUES FROM ('2020-02-01') TO ('2020-03-01')
USING COLUMNAR;
-- row partition
CREATE TABLE p2 PARTITION OF parent
FOR VALUES FROM ('2020-03-01') TO ('2020-04-01');
INSERT INTO parent VALUES ('2020-01-15', 10, 100, 'one thousand'); -- columnar
INSERT INTO parent VALUES ('2020-02-15', 20, 200, 'two thousand'); -- columnar
INSERT INTO parent VALUES ('2020-03-15', 30, 300, 'three thousand'); -- row
除了混合使用行存与列存的情况,我们还支持行存表与列存表之间的转换:
postgres@postgres=# select alter_table_set_access_method('my_columnar_table', 'heap');
NOTICE: creating a new table for public.my_columnar_table
NOTICE: moving the data of public.my_columnar_table
NOTICE: dropping the old public.my_columnar_table
NOTICE: renaming the new table to public.my_columnar_table
alter_table_set_access_method
-------------------------------
(1 row)
postgres@postgres=# \d+ my_columnar_table
Table "public.my_columnar_table"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
--------+---------+-----------+----------+---------+----------+--------------+-------------
id | integer | | | | plain | |
i1 | integer | | | | plain | |
i2 | bigint | | | | plain | |
n | numeric | | | | main | |
t | text | | | | extended | |
Access method: heap
postgres@postgres=# select alter_table_set_access_method('my_columnar_table', 'columnar');
NOTICE: creating a new table for public.my_columnar_table
NOTICE: moving the data of public.my_columnar_table
NOTICE: dropping the old public.my_columnar_table
NOTICE: renaming the new table to public.my_columnar_table
alter_table_set_access_method
-------------------------------
(1 row)
postgres@postgres=# \d+ my_columnar_table
Table "public.my_columnar_table"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
--------+---------+-----------+----------+---------+----------+--------------+-------------
id | integer | | | | plain | |
i1 | integer | | | | plain | |
i2 | bigint | | | | plain | |
n | numeric | | | | main | |
t | text | | | | extended | |
Access method: columnar
这样我们就可以在创建表后,根据实际数据的情况,灵活的选择列存储还是行存储,以获取更好的性能。




