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

使用DWS行列共存表

场景介绍

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

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

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

其核心优势在于:

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

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

实时场景下表类型对比(行存表 / 列存表 / 行列共存表)

表1 实时场景下表类型对比

对比维度

行存表(orientation='row')

HStore表(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列存特殊优化

不支持

支持

支持

约束限制

  • 仅9.1.1.100及以上版本支持
  • 需使用HStore表,即enable_hstore_opt参数设置为开。
  • 不支持物化视图。
  • 小批量的实时copy入库,相比HStore表,存在10%的性能劣化。
  • 行列混存表只能和行列混存表进行exchange,扩容重分布后,禁止使用exchange。
  • 行列混存表执行 DROP COLUMN后,被删除列仍会占用系统列号资源,因此表的可用列数上限仍受1600列限制。

使用建议

  • 仅OLTP场景(例如高频点查、写入):若对空间使用敏感,推荐使用行列共存表,兼顾空间与点查性能的折中方案,若空间不敏感,追求极致效果的场景推荐行存表。
  • 仅OLAP场景(例如统计分析、报表):推荐使用HStore的列存表。
  • 明细与统计查询并存,且字段冷热难以区分:推荐使用行列共存表模式,各个场景性能最优;
  • 实时入库上,推荐使用PBE AddBatch方式入库。
  • 建议不要轻易修改列定义,若触发数据重写,行列共存表会重写行存部分整个数据,性能开销较大。

语法参考

以下是创建行列共存表语法,更多请参见CREATE TABLE语法章节(仅9.1.1.100及以上版本支持)。

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

示例

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

使用行列混存表示例

  1. 建立一个简单的数据表。

    1
    2
    3
    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');
    

  2. 扩充表数据至20W。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    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;
    SELECT COUNT(*) FROM data;
    

  3. 构造一个简单的行列共存表。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    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'
    );
    

  4. 将数据导入行列共存表。

    1
    INSERT INTO rowmode_test1 SELECT * FROM data;
    

  5. 建立视图查询cudesc的辅助表,通过自定义函数实现。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    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;
    

  6. 查询DN节点名称。

    1
    SELECT * FROM pgxc_node;
    

    查看返回信息,记录DN1对应的node_name,例如dn_6001_6002。

  7. 直连DN1查询记录。其中datanode1替换成6查到的结果,例如dn_6001_6002。

    1
    2
    3
    4
    5
    6
    7
    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
    )
    $$;
    

    结果示例如下:

上述结果示例是行存部分,在列存辅助表中记录的示例数据,系统会在列存的辅助表中为每组 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 架构的深度绑定,充分继承了列存引擎在执行优化和压缩管理方面的优势,在维持优异点查性能的同时,显著降低了空间占用,为混合型负载提供了高性价比的解决方案。

行列共存表相关GUC参数

表2 行列共存表相关GUC参数

参数

描述

默认值

row_group_rows_threshold

用于控制行列混存表row模式/mix模式行存的压缩单元行数,即xxx行压缩为一个行存压缩单元row group。

  • 0表示由DWS自动选择最优值,压缩单元行数为200~2000。
  • 大于0时由用户指定压缩单位,压缩单元行数为1~10000,内核将按8对齐。

0

相关文档