GaussDB(DWS)行列共存表最佳实践

举报
Naibaoofficial 发表于 2025/06/10 17:46:08 2025/06/10
141 0 1
【摘要】 本文介绍了GaussDB(DWS)实时数仓中的行列共存表,行列共存表基于HStore OPT表,能够结合行存优势和列存优势,显著提升点查性能,博客简单介绍了建表说明,场景分析,性能总结以及简单的技术原理。

行列共存最佳实践

1. 前言

  • 适用版本:【9.1.1(及以上)】

在实际业务场景中,一张表往往既需要支持高并发的点查与更新(如订单、账单、日志等),也需要支持高效的批量查询与聚合分析(如统计报表、趋势分析)。传统的行存与列存各有所长:

  • 行存(Row Store):适合点查、写入频繁的实时场景,如根据订单号查询订单详情。
  • 列存(Column Store):适合大批量分析型查询,如统计一段时间内的总交易额。

GaussDB(DWS) 提供的行列共存表,是一种创新的混合存储模式:在一张表中同时存储行格式数据和列格式数据,两种格式各自独立维护、同步更新,由查询优化器根据实际查询路径选择最优访问方式。

其核心优势在于:

  • 无需拆表或复制数据:一张表即同时支持明细查询与批量分析;
  • 由系统自动选择最优路径:查询点查字段时走行存,查询聚合字段时走列存,无需开发额外处理;
  • 性能最优兼顾:在不牺牲 OLTP 性能的同时,兼具 OLAP 查询效率;
  • 兼容原有行表设计:无需调整应用结构,快速享受列存优化/行存优化带来的性能收益;
  • 统一管理、降低运维成本:一张物理表即覆盖两种场景,简化数据同步、备份与权限控制。

本最佳实践文档将介绍如何设计使用行列共存表结构,结合典型场景和性能对比,帮助您在业务系统中实现更高效的数据处理能力。

2. 使用方式概览

GaussDB(DWS) 在传统行存与列存之外,引入了 行列共存表(Row-Column Coexistence Table)。该表在物理存储层同时保存一份自研高性能行格式数据与一份列格式数据,由查询优化器根据查询特征自动选择最佳路径,无需用户干预。

用户通过设置表属性 storage_mode = mix 即可指定表的存储模式:

该选项表示,开启行列共存,系统同时保存一份行数据和一份列数据,自动决策使用路径,适用于同时存在明细与分析类查询的混合型业务。

注意:要启用 mix 模式,必须将表设置为 hstore_opt 表,并显式开启该能力。

2.1 创建行列共存表语法

CREATE TABLE <表名> (
    <列定义>
)
WITH (
    orientation = column,          -- 基于列式的存储架构
    enable_hstore_opt = on,        -- 开启 hstore_opt 能力
    storage_mode = 'mix'           -- 指定建立行列共存表
);

2.2 示例:

-- 行列共存模式:同时存储行格式与列格式数据,适用于明细+分析混合业务
CREATE TABLE tbl_mix (
    a INT,
    b TEXT
)
WITH (
    orientation = column,
    enable_hstore_opt = on,
    storage_mode = 'mix'
);

3. 场景分析

3.1 GaussDB(DWS)实时场景表类型使用说明对比(行存表 / 列存表 / 行列共存表)

对比维度 行存表(orientation=‘row’) col模式表(hstore_opt表) 行列共存表(storage_mode=‘mix’)
存储架构 原生行存引擎(不基于列存) HStore Opt 列存 自研行模式和列存HStore OPT表共存
点查性能(主键) 🟩 极优(5) 🟥 较差(2) 🟩 优(4)
批量入库性能 🟥 较差(2) 🟩 极优(5) 🟩 优(4)
实时入库性能 🟩 极优(5) 🟩 极优(5) 🟩 极优(5)
聚合/分析性能 ⬛ 差(1) 🟩 极优(5) 🟩 极优(5)
空间占用 ⬛ 高(1) 🟩 极低(5) 🟨 一般(3)
空间膨胀 🟩 极优(5) 🟩 极优(5) 🟩 优(4)
DWS列存特殊优化 ❌ 不支持 ✅ 支持 ✅ 支持

部分实验室参考数据:MIX 行列共存表性能表现概览(基于典型业务场景)

  1. 主键等值查询性能
    在高频主键等值访问场景中,行列共存表相较于列存表查询性能提升超过 5 倍;经过调优后,在部分场景中查询性能可优于传统行存表。

  2. 批量数据写入性能
    在批量 INSERT 和周期性归档写入场景中,行列共存表与 HStore Opt 表写入性能基本一致,未观察到显著差异。

  3. 小批量实时写入性能
    在小批量部分列 UPSERT 场景中,行列共存表写入性能较 HStore Opt 表提升超过 1 倍;在小批量全列 INSERT 场景中,写入性能下降约 10%。

  4. 存储空间占用
    基于 TPCH-1000x 与 TPCDS-1000x 数据集测试结果,行列共存表数据空间占用约为 HStore Opt 表的 2 倍, 约为行存表的 0.5 倍。

  5. 离线分析型查询性能
    在 TPCH 与 TPCDS 等复杂分析型查询场景中,行列共存表查询性能与 HStore Opt 表整体相当,能够在宽表、高选择性过滤条件下保持稳定的执行效率。

3.2 场景总结:

  • 仅 OLTP 场景(例如高频点查、写入):若对空间使用敏感,推荐使用行列共存表,兼顾空间与点查性能的的折中方案,若空间不敏感,追求极致效果的场景推荐行存表;
  • 仅 OLAP 场景(例如统计分析、报表):推荐使用hstore_opt的列存表;
  • 明细与统计查询并存,且字段冷热难以区分:推荐使用行列共存表模式,各个场景性能最优;

3.3 使用建议

  1. 实时入库上,推荐使用PBE AddBatch方式入库。
  2. 建议不要轻易修改列定义,若触发数据重写,行列共存表会重写行存部分整个数据,性能开销较大。

3.4 使用限制

  1. 不支持V3表。
  2. 不支持开启HStore Opt表的enable_light_update选项。
  3. 不支持binlog表,物化视图。
  4. 小批量的实时copy入库,相比HStore Opt表,存在10%的性能劣化。

4. 技术原理

4.1 自研行模式存储机制

在 GaussDB(DWS) 中,行列共存表的架构设计与列存引擎深度融合,特别是在底层存储结构上,二者共用统一的存储管理框架。对于行列共存表的行存部分,其核心的压缩存储单元被称为 RowGroup

RowGroup是基于列定义动态组织的行块单元,系统会根据字段数量和行宽,自动选择合适的行数(通常为 200 至 2000 行)聚合成一个 RowGroup,用作最小压缩处理单元。每个 RowGroup 都具有独立的压缩编码和访问路径,具备良好的数据局部性和压缩性能。

在与列存的协同管理上,RowGroup的结构与列存 CU(Compressed Unit)紧密绑定。每列的同一 CU(即具有相同 CUID)会统一归入一个 CU Group,而该 CU Group 中的数据则被组织为多个 RowGroup。系统会在辅助存储表中插入一条特殊标识记录(列号为 -16),用于标记该 CU Group 对应的 RowGroup 存储结构。

下图展示了 RowGroup 与列存 CU 的对应关系和存储结构示意:

RowGroup存储单元示意图

举例说明如下:

-- 建立一个简单的数据表
DROP TABLE IF EXISTS data;
CREATE TABLE data(a INT, b BIGINT, c VARCHAR(10), d VARCHAR(10));
INSERT INTO data values(generate_series(1,100),1,'asdfasdf','gergqer');

-- 扩充数据至20W
INSERT INTO data select * from data;
INSERT INTO data select * from data;
INSERT INTO data select * from data;
INSERT INTO data select * from data;
INSERT INTO data select * from data;
INSERT INTO data select * from data;
INSERT INTO data select * from data;
INSERT INTO data select * from data;
INSERT INTO data select * from data;
INSERT INTO data select * from data;
INSERT INTO data select * from data;

-- 构造一个简单的行列共存表
DROP TABLE IF EXISTS rowmode_test1;
CREATE TABLE rowmode_test1 (
    a INT,
    b BIGINT,
    c VARCHAR(10),
    d VARCHAR(10)
)
WITH (
    orientation = column,
    enable_hstore_opt = on,
    storage_mode = 'mix'
);

-- 将数据导入行列共存ROW模式表
INSERT INTO rowmode_test1 SELECT * FROM data;

-- 建立视图查询cudesc的辅助表
DROP FUNCTION IF EXISTS get_cudesc_data_info;
CREATE FUNCTION get_cudesc_data_info(target_table_name text)
RETURNS TABLE (
    col_id int, 
    cu_id oid, 
    min text, 
    max text, 
    row_count int, 
    cu_mode int, 
    size bigint, 
    cu_pointer text, 
    magic int, 
    extra text, 
    src_info text, 
    xid xid, 
    dr_bucket_id int, 
    dict_key bytea, 
    cu_data bytea,
    cu_data_size_kb numeric
) AS
$$
DECLARE
    cudesc_relid oid;
    cudesc_relname text;
    query text;
BEGIN
    SELECT relcudescrelid INTO cudesc_relid FROM pg_class WHERE relname = target_table_name;
    SELECT relname INTO cudesc_relname FROM pg_class WHERE oid = cudesc_relid;
    IF cudesc_relname IS NULL THEN
        RAISE NOTICE 'CU descriptor table not found for %.', target_table_name;
        RETURN;
    END IF;
    query := FORMAT('SELECT *, octet_length(cu_data)::numeric/1024 AS cu_data_size_kb FROM cstore.%I', cudesc_relname);
    RETURN QUERY EXECUTE query USING target_table_name;
END;
$$ LANGUAGE plpgsql;

-- 直连DN1查询记录,DN名称可通过pgxc_node表的node_name字段确认。
EXECUTE DIRECT ON(datanode1) $$
SELECT col_id, cu_id, row_count, size, cu_pointer
FROM (
    SELECT col_id, cu_id, row_count, size, cu_pointer
    FROM get_cudesc_data_info('rowmode_test1') where col_id = -16 and cu_id = 1002
)
$$;

-- 结果示例如下:
 col_id | cu_id | row_count |  size  |  cu_pointer  
--------+-------+-----------+--------+--------------
    -16 |  1002 |     60000 | 245760 | 1|2000|30|0|
(1 row)

上述结果示例是行存部分,在列存辅助表中记录的示例数据,系统会在列存的辅助表中为每组 RowGroup 插入一条特殊标识记录,其字段含义如下:

  • col_id:固定为 -16,用于标识该记录对应的是 RowGroup 元信息,而非普通列数据。
  • cu_id:与列存的 CU 编号规则一致,表示该组 RowGroup 归属于的 CU ID。
  • row_count:表示当前 CU Group 中总共包含的行数,如示例中为 60000 行。
  • size:记录这些行经过压缩后的实际磁盘占用空间(单位为字节)。
  • cu_pointer:该字段存储了 RowGroup 的结构信息,由四个由 | 分隔的字段组成:
    1. 版本号:当前为 1,表示该 RowGroup 使用的元数据版本;
    2. 单组行数:每个 RowGroup 中包含的最大行数(如 2000);
    3. 组数:表示该 CU 中包含的 RowGroup 数量(如 30);
    4. 起始偏移:该组 RowGroup 在物理存储文件中的起始偏移地址(如 0)。

总体而言,自研行存部分通过与 CU 架构的深度绑定,充分继承了列存引擎在执行优化和压缩管理方面的优势,在维持优异点查性能的同时,显著降低了空间占用,为混合型负载提供了高性价比的解决方案。

4.2 行列共存存储机制

在 GaussDB(DWS) 的行列共存架构中,模式通过同时保留一份行格式数据和一份列格式数据,实现对不同查询特征的自动适配。该机制并非简单的数据冗余,而是与列存引擎深度协同,通过列存固有的过滤优化能力实现更智能的 I/O 路径控制。

在实际查询过程中,系统会基于列存中每个 CU Group 的统计信息与过滤策略(如 Filter 下推、Rough Check 等),对待访问的数据进行评估。当某个 CU Group 经粗过滤判定为不满足查询条件或命中率极低时,系统将跳过对应的列存路径,转而访问对应的 RowGroup(行格式)数据,或直接裁剪掉该数据块以减少 I/O 成本。

这一机制确保系统能够在列存提供高压缩、高吞吐优势的同时,保留行存对局部数据的灵活访问能力,特别适用于混合查询场景下的高效数据调度。得益于这种精细化的路径筛选能力,Mix 模式在大多数业务负载中可以显著降低整体 I/O 开销,同时提升数据命中率与查询性能。

5. 点查场景的第二种选择bitmap index

在实际业务中,针对主键、订单号、用户 ID 等字段的点查场景非常常见。传统做法是通过建立索引来提升查询性能,这类方式在小数据量或高选择性字段上效果显著。

但在大数据量、宽表结构或批量入库场景下,常规索引机制逐渐暴露出以下问题:

  • 索引结构维护成本高,空间占用大
  • 入库或更新性能受限,批量写入开销明显
  • 对于重复值较多的字段,索引命中率低,收益不明显

为了解决这些问题,GaussDB(DWS) 对hstore_opt表提供了轻量级的列级索引机制 —— bitmap index,在保证查询加速能力的同时,最大程度降低空间和写入成本,特别适用于点查类字段的性能优化。

([bitmap index的建表语法]](https://support.huaweicloud.com/sqlreference-910-dws/dws_06_0177.html))。

在 HStore Opt 表中,bitmap index 索引机制会根据 CU 内的数据分布特征,灵活采用位图索引、字典编码、Bloom Filter 等优化手段,构建轻量化索引结构与压缩方案。结合 GaussDB(DWS) 的列存执行机制,可在保持极低空间开销的同时,有效提升点查类查询的执行效率,显著降低整体 I/O 成本。

在行列共存表中,RowGroup 的引入进一步增强了点查路径的过滤能力。系统通过在 RowGroup 级别实现 Bloom Filter 过滤,能够在更精细的粒度上快速判断数据是否命中,从而减少无效数据访问,优化 I/O 性能。在 mix 模式下,RowGroup 过滤与列存中的 bitmap index可协同工作,融合两种索引机制的优势,实现更精准的数据裁剪和更高效的查询路径选择。

该机制特别适用于高频点查、高重复值字段、冷热字段混合等场景,在提升性能的同时,有效控制存储成本。

实验室测试结果表明:在大表场景下,Bitmap Index 索引空间占用相比 B-Tree 索引可减少约 80%,且不存在空间膨胀问题。同时,在典型点查查询中,行列共存表使用 Bitmap Index 相较 HStore Opt 表使用 B-Tree 索引,查询性能提升可达 4 至 5 倍。

示例如下:

-- 构造数据表
DROP TABLE IF EXISTS data;
CREATE TABLE data(a INT, b BIGINT, c VARCHAR(10), d VARCHAR(10));
INSERT INTO data values(generate_series(1,60000),1,'asdfasdf','gergqer');
INSERT INTO data select * from data;
INSERT INTO data select * from data;
INSERT INTO data select * from data;
INSERT INTO data select * from data;
INSERT INTO data select * from data;

-- 构造一个简单的行列共存Mix模式表
DROP TABLE IF EXISTS mixmode_test1;
CREATE TABLE mixmode_test1 (
    a INT,
    b BIGINT,
    c VARCHAR(10),
    d VARCHAR(10)
)
WITH (
    orientation = column,
    enable_hstore_opt = on,
    storage_mode = 'mix',
    bitmap_columns = 'a'
);

-- 创建 B-Tree 索引
CREATE INDEX idx_data_a_btree ON mixmode_test1(a);

-- 将数据导入行列共存ROW模式表
INSERT INTO mixmode_test1 SELECT * FROM data;

-- 使用索引扫描进行点查,查找 a = 42 的记录, 打印执行计划
SET enable_seqscan = off;
SET enable_indexscan = on;
SET enable_fast_query_shipping = off;
EXPLAIN PERFORMANCE SELECT * FROM mixmode_test1 WHERE a = 42;

-- 结果部分:可见选择了索引扫描
    Predicate Information (identified by plan id)    
 ----------------------------------------------------
   3 --CStore Index Heap Scan on public.mixmode_test1
         Recheck Cond: (mixmode_test1.a = 42)
   4 --CStore Index Ctid Scan
         Index Cond: (mixmode_test1.a = 42)

-- 使用顺序扫描进行点查,查找 a = 42 的记录, 打印执行计划
SET enable_seqscan = on;
SET enable_indexscan = off;
SET enable_bitmapscan = off;
SET enable_fast_query_shipping = off;
EXPLAIN PERFORMANCE SELECT * FROM mixmode_test1 WHERE a = 42;

-- 结果部分:使用bitmap_column点查执行计划中选择了顺序扫描, 可看到Auto Index过滤掉绝大部分数据
       Predicate Information (identified by plan id)      
 ---------------------------------------------------------
   3 --CStore Scan on public.mixmode_test1
         Rows Removed by Filter: 1919968
         Other filter info: (Autoindex: 1856000)
         CU Predicate Filter: (mixmode_test1.a = 42)
         Pushdown Predicate Filter: (mixmode_test1.a = 42)

6. 总结

对于同时存在明细查询与分析需求的业务场景,如何在一张表中兼顾查询性能、写入效率与存储成本,往往是架构设计中的难点。

GaussDB(DWS) 的行列共存机制,正是为此类复杂负载场景量身打造的解决方案,行列共存表提供了统一的双格式存储,让查询可以在行路径与列路径之间灵活切换,始终选择更优的访问方式,无需人为干预。

同时,bitmap index 轻量索引也让GaussDB(DWS) 在无需使用传统索引的情况下,依然可以获得优异的点查性能,特别适用于宽表结构或重复值较多的筛选字段。

对于用户而言,行列共存架构不需要额外的运维负担,不改变已有建表方式,即可获得更灵活的查询能力和更高的数据利用效率。无论是实时入库、报表分析,还是复杂混合查询,行列共存都能在性能与成本之间找到更合适的平衡点,帮助业务更稳定、更高效地运行。

7. 参考文献

  1. GaussDB(DWS)存储引擎:从CU入手优化HStore表
  2. GaussDB(DWS)基本IO框架
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

作者其他文章

评论(0

抱歉,系统识别当前为高风险访问,暂不支持该操作

    全部回复

    上滑加载中

    设置昵称

    在此一键设置昵称,即可参与社区互动!

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。