更新时间: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系统函数”章节。

自动加载示例:

-- 创建普通表并插入数据。
htapdb=# DROP TABLE IF EXISTS htap_gs_imcv_flush_nopart;
htapdb=# CREATE TABLE htap_gs_imcv_flush_nopart(
    c1 bigint,
    c2 varchar(256)
) WITH (STORAGE_TYPE=USTORE) DISTRIBUTE BY HASH(c1);
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

-- 创建一级分区表并插入数据。
htapdb=# DROP TABLE IF EXISTS htap_gs_imcv_flush_part;
htapdb=# CREATE TABLE htap_gs_imcv_flush_part (
    c1 bigint,
    c2 bigint,
    c3 varchar(256)
) DISTRIBUTE BY HASH(c1)
PARTITION BY RANGE (c1)(
    PARTITION htap_gs_imcv_flush_part_p1 VALUES LESS THAN (2),
    PARTITION htap_gs_imcv_flush_part_p2 VALUES LESS THAN (3)
);
CREATE TABLE

htapdb=# INSERT INTO htap_gs_imcv_flush_part (c1, c2, c3)
SELECT
    CASE
        WHEN g <= 256000 THEN 1
        ELSE 2
    END AS c1,
    g AS c2,
    'data_' || g AS c3
FROM generate_series(1, 512000) AS g;

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

-- DN节点查看IMCV信息(其中,rowgroup_num为表的实际行组数量,populated_rowgroup_num为表已完成加载的行组数量)。
htapdb=# SELECT rel_name, partition_name, rowgroup_num, populated_rowgroup_num FROM gs_htap_imcv_info();
          rel_name         |        partition_name      | rowgroup_num | populated_rowgroup_num 
---------------------------+----------------------------+--------------+------------------------
 htap_gs_imcv_flush_part   | htap_gs_imcv_flush_part_p1 |           2 |                     0
 htap_gs_imcv_flush_part   |                            |            0 |                      0
 htap_gs_imcv_flush_part   | htap_gs_imcv_flush_part_p2 |           2 |                      0
 htap_gs_imcv_flush_nopart |                            |            2 |                      2

-- 等待一段时间后查看IMCV信息(等待后台加载完成)。
htapdb=# SELECT rel_name, partition_name, rowgroup_num, populated_rowgroup_num FROM gs_htap_imcv_info();
          rel_name         |        partition_name      | rowgroup_num | populated_rowgroup_num 
---------------------------+----------------------------+--------------+------------------------
 htap_gs_imcv_flush_part   | htap_gs_imcv_flush_part_p1 |           2 |                     2
 htap_gs_imcv_flush_part   |                            |            0 |                      0
 htap_gs_imcv_flush_part   | htap_gs_imcv_flush_part_p2 |           2 |                     2
 htap_gs_imcv_flush_nopart |                            |            2 |                      2

手动加载示例:

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

Time: 406.391 ms
htapdb=# \time off
Timing is off.
htapdb=# ALTER TABLE htap_gs_imcv_flush_part NOCOLVIEW;
ALTER TABLE
htapdb=# ALTER TABLE htap_gs_imcv_flush_part COLVIEW;
ALTER TABLE
htapdb=# \timing on
Timing is on.
htapdb=# SELECT * FROM gs_imcv_flush ('htap_gs_imcv_flush_part', '', 2);
 total | success | error | skip 
-------+---------+-------+------
 4    | 4      | 0     | 0
(1 row)

Time: 332.194 ms
htapdb=# \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()接口手动触发重建。重建效率与参数设置的并行度有关。

自动重建示例:

-- 查看同步时间间隔和脏页阈值。
htapdb=# SHOW htap_flush_interval;
 htap_flush_interval 
---------------------
 5min
(1 row)
htapdb=# SHOW htap_flush_pagenum;
 htap_flush_pagenum 
--------------------
 512
(1 row)
-- 向一级分区表htap_gs_imcv_flush_part中更新数据。
htapdb=# UPDATE htap_gs_imcv_flush_part SET c3 = 'abcd' WHERE c2 IN (2, 20002);
UPDATE 2
-- 查看脏页数(未达重建条件的脏页阈值)。
htapdb=# SELECT rel_name, sum(dirty_page_count) as dirty_page_count FROM gs_global_htap_tmu_chunk_meta('htap_gs_imcv_flush_part','',0) GROUP BY rel_name;
          rel_name       | dirty_page_count 
-------------------------+------------------
 htap_gs_imcv_flush_part |               2
(1 row)
-- 等待5min后查看脏页数,此时后台重建完成(达到设置的重建间隔后触发后台重建完成)。
htapdb=# SELECT rel_name, sum(dirty_page_count) as dirty_page_count FROM gs_global_htap_tmu_chunk_meta('htap_gs_imcv_flush_part','',0) GROUP BY rel_name;
          rel_name       | dirty_page_count 
-------------------------+------------------
 htap_gs_imcv_flush_part |                0
(1 row)
-- 向一级分区表htap_gs_imcv_flush_part中插入足够数据(大于脏页阈值)。
htapdb=# INSERT INTO htap_gs_imcv_flush_part (c1, c2, c3)
SELECT 1, 1, 'c3' FROM generate_series(1, 120000);
INSERT 120000
-- 查看脏页数。
htapdb=# SELECT rel_name, sum(dirty_page_count) as dirty_page_count FROM gs_htap_tmu_chunk_meta('htap_gs_imcv_flush_part','',0) GROUP BY rel_name;
          rel_name       | dirty_page_count 
-------------------------+------------------
 htap_gs_imcv_flush_part |             557
(1 row)
-- 等待后台自动重建完成后再次查看脏页数。
htapdb=# SELECT rel_name, sum(dirty_page_count) as dirty_page_count FROM gs_htap_tmu_chunk_meta('htap_gs_imcv_flush_part','',0) GROUP BY rel_name;
          rel_name       | dirty_page_count 
-------------------------+------------------
 htap_gs_imcv_flush_part |                0
(1 row) 

手动重建示例:

-- 使用gs_imcv_flush()接口手动触发重建的方式与手动触发加载的方式相同。
-- 向一级级分区表htap_gs_imcv_flush_part中插入数据。
htapdb=# UPDATE htap_gs_imcv_flush_part SET c3 = 'efgh' WHERE c1=1;
UPDATE 24
-- 手动触发非强制全表重建(第四个参数缺省或为0,只重建有脏页标记的rowgroup)。
htapdb=# SELECT * FROM gs_imcv_flush ('htap_gs_imcv_flush_part', '', 2);
 total | success | error | skip 
-------+---------+-------+------
 4    | 1       | 0     | 3
-- 手动触发强制全表重建(第四个参数为1,基于行存重新构建全部rowgroup)。
htapdb=# SELECT * FROM gs_imcv_flush ('htap_gs_imcv_flush_part', '', 2,1);
 total | success  | error | skip 
-------+----------+-------+------
 4    | 4      | 0     | 0
(1 row)

htapdb=# DROP TABLE htap_gs_imcv_flush_nopart;
DROP TABLE

htapdb=# DROP TABLE htap_gs_imcv_flush_part;
DROP TABLE

相关文档