更新时间:2026-07-28 GMT+08:00
分享

列存数据维护

列存数据加载

列存数据加载是指首次根据行存数据生成该表对应的列存数据。

在启用异步加载模式后,通过DDL命令为表创建IMCV时,系统仅会建立列存的元数据结构,并不会立刻生成实际的列存数据。

系统提供两种列存数据的加载方式:

  • 后台HTAP Worker自动加载

    系统会在后台调度HTAP Worker,对目标表自动完成列存数据的构建。

    该方式无需人工干预,但其执行时间可能受到后台HTAP Worker的线程数量(由GUC参数htap_flush_workers决定)、调度情况的影响。

  • 手动触发加载

    用户可直接调用gs_imcv_flush()接口,立即开始对指定表进行列存数据加载。

    当后台加载未及时完成时,用户可主动执行该接口加速加载进程。加载效率与参数设置的并行度有关。

  • HTAP Worker数量由GUC参数htap_flush_workers决定,设置方法请参见《参考》中“数据库运行参数说明 > GUC参数说明 > HTAP混合负载”章节。
  • gs_imcv_flush是用于列存数据加载/重建的系统函数,使用方法请参见《参考》中“SQL参考 > 函数和操作符 > 系统管理函数 > HTAP系统函数”章节。

自动加载示例:

-- 创建普通表并插入数据。
gaussdb=# DROP TABLE IF EXISTS htap_gs_imcv_flush_nopart;
gaussdb=# CREATE TABLE htap_gs_imcv_flush_nopart(
    c1 bigint,
    c2 varchar(256)
) WITH (STORAGE_TYPE=USTORE);
CREATE TABLE

htapdb=# INSERT INTO htap_gs_imcv_flush_nopart (c1, c2)
SELECT
    g AS c1,
    'data_' || g AS c2
FROM generate_series(1, 256000) AS g;
INSERT 0 256000

-- 创建二级分区表并插入数据。
gaussdb=# DROP TABLE IF EXISTS htap_gs_imcv_flush_subpart;
gaussdb=# CREATE TABLE htap_gs_imcv_flush_subpart (
    c1 bigint,
    c2 bigint,
    c3 varchar(256)
)
PARTITION BY RANGE (c1)
SUBPARTITION BY LIST (c2)
(
    PARTITION htap_gs_imcv_flush_subpart_p1 VALUES LESS THAN (16001)
    (
        SUBPARTITION htap_gs_imcv_flush_subpart_p1_sp1 VALUES (1),
        SUBPARTITION htap_gs_imcv_flush_subpart_p1_sp2 VALUES (2)
    ),
    PARTITION htap_gs_imcv_flush_subpart_p2 VALUES LESS THAN (32001)
    (
        SUBPARTITION htap_gs_imcv_flush_subpart_p2_sp3 VALUES (3),
        SUBPARTITION htap_gs_imcv_flush_subpart_p2_sp4 VALUES (4)
    )
);
CREATE TABLE

-- 子分区 p1_sp1: c1 < 16001, c2 = 1。
gaussdb=# INSERT INTO htap_gs_imcv_flush_subpart
SELECT 
    10000 AS c1, 
    1 AS c2, 
    'data_for_p1_sp1_' || generate_series::text AS c3
FROM generate_series(1, 240000);
INSERT 0 240000

-- 子分区 p1_sp2: c1 < 16001, c2 = 2。
gaussdb=# INSERT INTO htap_gs_imcv_flush_subpart
SELECT 
    10000 AS c1, 
    2 AS c2, 
    'data_for_p1_sp2_' || generate_series::text AS c3
FROM generate_series(1, 240000);
INSERT 0 240000

-- 子分区 p2_sp3: 16001 <= c1 < 32001, c2 = 3。
gaussdb=# INSERT INTO htap_gs_imcv_flush_subpart
SELECT 
    20000 AS c1, 
    3 AS c2, 
    'data_for_p2_sp3_' || generate_series::text AS c3
FROM generate_series(1, 240000);
INSERT 0 240000

-- 子分区 p2_sp4: 16001 <= c1 < 32001, c2 = 4。
gaussdb=# INSERT INTO htap_gs_imcv_flush_subpart
SELECT 
    20000 AS c1, 
    4 AS c2, 
    'data_for_p2_sp4_' || generate_series::text AS c3
FROM generate_series(1, 240000);
INSERT 0 240000

-- 为非分区表和分区表创建IMCV。
gaussdb=# ALTER TABLE htap_gs_imcv_flush_nopart COLVIEW;
ALTER TABLE

gaussdb=# ALTER TABLE htap_gs_imcv_flush_subpart COLVIEW;
ALTER TABLE

-- 查看IMCV信息(其中,rowgroup_num为表的实际行组数量,populated_rowgroup_num为表已完成加载的行组数量)。
gaussdb=# SELECT rel_name, partition_name, subpartition_name, rowgroup_num, populated_rowgroup_num FROM gs_htap_imcv_info();
          rel_name          |        partition_name         |         subpartition_name         | rowgroup_num | populated_rowgroup_num 
----------------------------+-------------------------------+-----------------------------------+--------------+------------------------
 htap_gs_imcv_flush_subpart | htap_gs_imcv_flush_subpart_p1 | htap_gs_imcv_flush_subpart_p1_sp1 |            2 |                      0
 htap_gs_imcv_flush_subpart | htap_gs_imcv_flush_subpart_p1 | htap_gs_imcv_flush_subpart_p1_sp2 |            2 |                      0
 htap_gs_imcv_flush_subpart | htap_gs_imcv_flush_subpart_p1 |                                   |            0 |                      0
 htap_gs_imcv_flush_subpart |                               |                                   |            0 |                      0
 htap_gs_imcv_flush_subpart | htap_gs_imcv_flush_subpart_p2 | htap_gs_imcv_flush_subpart_p2_sp3 |            2 |                      0
 htap_gs_imcv_flush_subpart | htap_gs_imcv_flush_subpart_p2 | htap_gs_imcv_flush_subpart_p2_sp4 |            2 |                      0
 htap_gs_imcv_flush_subpart | htap_gs_imcv_flush_subpart_p2 |                                   |            0 |                      0
 htap_gs_imcv_flush_nopart  |                               |                                   |            2 |                      0

-- 等待一段时间后查看IMCV信息(等待后台加载完成)。
gaussdb=# SELECT rel_name, partition_name, subpartition_name, rowgroup_num, populated_rowgroup_num FROM gs_htap_imcv_info();
          rel_name          |        partition_name         |         subpartition_name         | rowgroup_num | populated_rowgroup_num 
----------------------------+-------------------------------+-----------------------------------+--------------+------------------------
 htap_gs_imcv_flush_subpart | htap_gs_imcv_flush_subpart_p1 | htap_gs_imcv_flush_subpart_p1_sp1 |            2 |                      2
 htap_gs_imcv_flush_subpart | htap_gs_imcv_flush_subpart_p1 | htap_gs_imcv_flush_subpart_p1_sp2 |            2 |                      2
 htap_gs_imcv_flush_subpart | htap_gs_imcv_flush_subpart_p1 |                                   |            0 |                      0
 htap_gs_imcv_flush_subpart |                               |                                   |            0 |                      0
 htap_gs_imcv_flush_subpart | htap_gs_imcv_flush_subpart_p2 | htap_gs_imcv_flush_subpart_p2_sp3 |            2 |                      2
 htap_gs_imcv_flush_subpart | htap_gs_imcv_flush_subpart_p2 | htap_gs_imcv_flush_subpart_p2_sp4 |            2 |                      2
 htap_gs_imcv_flush_subpart | htap_gs_imcv_flush_subpart_p2 |                                   |            0 |                      0
 htap_gs_imcv_flush_nopart  |                               |                                   |            2 |                      2

手动加载示例:

-- 重新创建非分区表htap_gs_imcv_flush_nopart与二级分区表htap_gs_imcv_flush_subpart并插入数据,创建方式与上文示例相同。
-- 1.指定关系加载列存数据。
-- (1)指定非分区表加载列存数据。
gaussdb=# SELECT * FROM gs_imcv_flush ('htap_gs_imcv_flush_nopart');
 total | success | error | skip 
-------+---------+-------+------
 2     | 2       | 0     | 0
(1 row)
-- (2)指定分区表加载列存数据。
-- (2.1)指定二级分区表加载列存数据。
gaussdb=# SELECT * FROM gs_imcv_flush ('htap_gs_imcv_flush_subpart', 'htap_gs_imcv_flush_subpart_p1_sp1');
 total | success| error | skip 
-------+--------+-------+------
 2     | 2      | 0     | 0
(1 row)
-- (2.2)指定一级分区表加载列存数据(sp2加载成功,sp1已加载过,本次加载跳过)。
gaussdb=# SELECT * FROM gs_imcv_flush ('htap_gs_imcv_flush_subpart', 'htap_gs_imcv_flush_subpart_p1');
 total | success| error | skip 
-------+--------+-------+------
 4     | 2      | 0     | 2
(1 row)
-- (2.3)指定基表加载列存数据(p2加载成功,p1已加载过,本次加载跳过)。
gaussdb=# SELECT * FROM gs_imcv_flush ('htap_gs_imcv_flush_subpart', '');
 total| success| error | skip 
------+--------+-------+------
 8    | 4      | 0     | 4
(1 row)
-- 2.指定加载列存数据的并行度。
gaussdb=# ALTER TABLE htap_gs_imcv_flush_subpart NOCOLVIEW;
ALTER TABLE

gaussdb=# ALTER TABLE htap_gs_imcv_flush_subpart COLVIEW;
ALTER TABLE

gaussdb=# \timing on
Timing is on.

gaussdb=# SELECT * FROM gs_imcv_flush ('htap_gs_imcv_flush_subpart', '', 1);
 total| success| error | skip 
------+--------+-------+------
 8    | 8      | 0     | 0
(1 row)

Time: 406.391 ms

gaussdb=# \time off
Timing is off.

gaussdb=# ALTER TABLE htap_gs_imcv_flush_subpart NOCOLVIEW;
ALTER TABLE

gaussdb=# ALTER TABLE htap_gs_imcv_flush_subpart COLVIEW;
ALTER TABLE

gaussdb=# \timing on
Timing is on.

gaussdb=# SELECT * FROM gs_imcv_flush ('htap_gs_imcv_flush_subpart', '', 2);
 total| success| error | skip 
------+--------+-------+------
 8    | 8      | 0     | 0
(1 row)

Time: 192.194 ms

gaussdb=# \time off
Timing is off.

列存数据重建

在完成列存加载之后,后续所有的DML(INSERT/UPDATE/DELETE)都会产生新的增量数据。这些增量由实时事务单元TMU维护,但不会即时同步到已经加载的列存数据中。

当这些增量积累过多时,列存数据会逐渐落后于实际行存内容,从而影响列存扫描性能。此时需要进行列存数据重建,把累积的增量同步到列存中,保持列存数据的新鲜度和查询性能。

系统同样提供两种重建方式:

  • 后台HTAP Worker自动重建

    当增量数据达到一定规模,或者距离上一次重建一定时间后,系统在后台自动触发列存重建任务。

    重建耗时同样会受到后台线程数量和调度的影响。

    后台自动重建的触发条件:

    • 增量数据所涉及的页面数达到一定比例,由GUC参数htap_flush_pagenum控制。
    • 距离上次重建达到一定的时间间隔,由GUC参数htap_flush_interval控制。

    设置方法请参见《参考》中“数据库运行参数说明 > GUC参数说明 > HTAP混合负载”章节。

  • 手动触发重建

    如果后台重建未及时执行,或者用户希望立即更新列存内容,可直接调用gs_imcv_flush()接口手动触发重建。

    重建效率与参数设置的并行度有关。

自动重建示例:

-- 查看同步时间间隔和脏页阈值。
gaussdb=# SHOW htap_flush_interval;
 htap_flush_interval 
---------------------
 5min
(1 row)
gaussdb=# SHOW htap_flush_pagenum;
 htap_flush_pagenum 
--------------------
 512
(1 row)

-- 更新子分区p1_sp1中的前10行(c2 = 1, c1 = 10000)。
gaussdb=# UPDATE htap_gs_imcv_flush_subpart
SET c3 = c3 || '_updated'
WHERE c1 = 10000
  AND c2 = 1
LIMIT 10;
UPDATE 10

-- 查看脏页数(未达重建条件的脏页阈值)。
gaussdb=# SELECT rel_name, sum(dirty_page_count) as dirty_page_count FROM gs_htap_tmu_chunk_meta('htap_gs_imcv_flush_subpart','',0) GROUP BY rel_name;
          rel_name          | dirty_page_count 
----------------------------+------------------
 htap_gs_imcv_flush_subpart |                1
(1 row)

-- 等待5min后查看脏页数,此时后台重建完成(达到设置的重建间隔后触发后台重建完成)。
gaussdb=# SELECT rel_name, sum(dirty_page_count) as dirty_page_count FROM gs_htap_tmu_chunk_meta('htap_gs_imcv_flush_subpart','',0) GROUP BY rel_name;
          rel_name          | dirty_page_count 
----------------------------+------------------
 htap_gs_imcv_flush_subpart |                0
(1 row)

-- 向二级分区表htap_gs_imcv_flush_subpart中插入足够数据(大于脏页阈值)。
gaussdb=# INSERT INTO htap_gs_imcv_flush_subpart (c1, c2, c3)
SELECT 
    10000 AS c1,  -- 属于 p1 ( < 16001 )
    1 AS c2,      -- 属于 sp1
    'data_p1_sp1_' || g::text AS c3
FROM generate_series(1, 400000) AS g;
INSERT 0 400000

-- 查看脏页数。
gaussdb=# SELECT rel_name, sum(dirty_page_count) as dirty_page_count FROM gs_htap_tmu_chunk_meta('htap_gs_imcv_flush_subpart','',0) GROUP BY rel_name;
          rel_name          | dirty_page_count 
----------------------------+------------------
 htap_gs_imcv_flush_subpart |             2719
(1 row)
-- 等待后台自动重建完成后再次查看脏页数。
gaussdb=# SELECT rel_name, sum(dirty_page_count) as dirty_page_count FROM gs_htap_tmu_chunk_meta('htap_gs_imcv_flush_subpart','',0) GROUP BY rel_name;
          rel_name          | dirty_page_count 
----------------------------+------------------
 htap_gs_imcv_flush_subpart |                0
(1 row)

手动重建示例:

-- 使用gs_imcv_flush()接口手动触发重建的方式与手动触发加载的方式相同。
-- 向二级分区表htap_gs_imcv_flush_subpart中插入数据。
-- 更新子分区p1_sp1中的前 10 行(c2 = 1, c1 = 10000)。
gaussdb=# UPDATE htap_gs_imcv_flush_subpart
SET c3 = c3 || '_updated2'
WHERE c1 = 10000
  AND c2 = 1
LIMIT 10;
UPDATE 10

-- 手动触发非强制全表重建(第四个参数缺省或为0,只重建有脏页标记的rowgroup)。
gaussdb=# SELECT * FROM gs_imcv_flush ('htap_gs_imcv_flush_subpart', '', 2);
 total  | success | error | skip 
--------+---------+-------+------
 11     | 1       | 0     | 10

-- 手动触发强制全表重建(第四个参数为1,基于行存重新构建全部rowgroup)。
gaussdb=# SELECT * FROM gs_imcv_flush ('htap_gs_imcv_flush_subpart', '', 2, 1);
 total  | success | error | skip 
--------+---------+-------+------
 11     | 11      | 0     | 0
(1 row)

gaussdb=# DROP TABLE htap_gs_imcv_flush_nopart;
DROP TABLE

gaussdb=# DROP TABLE htap_gs_imcv_flush_subpart;
DROP TABLE

相关文档