宽表存储格式 V3
本功能自 Apache Doris 4.1.0 版本开始支持。在建表 PROPERTIES 中设置 "storage_format" = "V3" 即可启用。
宽表存储格式 V3 针对列数极多的表对 Segment 元数据布局做了重构。通过将列元数据从 Footer 中拆出、按需加载,显著降低 Segment 打开延迟与内存占用。
适用场景
如果你的查询观察到 "Segment 打开"阶段非常慢、内存占用异常高,且符合下表中任一场景,建议启用 V3 存储格式:
| 用户场景 | 典型表现 | 是否推荐 V3 |
|---|---|---|
| 宽表查询(几百至几千列) | 即使只查少数列,Segment 打开仍占用大量时间和内存 | 推荐 |
含 VARIANT 列的表 | 子列动态展开后,实际参与存储的列数远超表面定义 | 推荐 |
| 部署在对象存储 / 分层存储 | 数据放在 S3、OSS 等远端存储上,冷查询延迟敏感 | 推荐 |
| 普通表(几十列以内) | Segment 打开开销可忽略 | 无需切换 |
典型痛点示例:在一张 7,000 列、共 10,000 个 Segment 的宽表上,打开 Segment 需要约 65 秒,过程中峰值内存占用达 60 GB。这部分开销与查询是否真的用到这些列无关,属于纯粹的"上车成本"。
问题根因
旧格式把所有列的元数据(ColumnMetaPB)集中打包在 Segment 文件末尾的 Footer 里,导致:
- 元数据全量加载:打开 Segment 必须先把整个 Footer 反序列化,哪怕查询只用到两列也要付全部代价。
- Footer 体积膨胀:当列数到达几千时,Footer 本身就能膨胀到几 MB。
- 远端存储放大效应:对象存储上的网络延迟与高读取成本使上述开销进一步被放大。
换言之,即使一条 SQL 只查询 2 个列,Doris 也要先把这个 Segment 内所有列的元数据全部读到内存里、反序列化后才能开始扫描。列越多、Segment 越多,这部分开销越夸张。
V3 关键优化
V3 从三个维度对存储格式进行重构:
优化一:列元数据按需加载
V3 把列元数据从 Footer 中拆出,放到文件中独立的区域,Footer 只保留指向各列元数据的轻量指针。

打开 Segment 时,系统只读一个精简的 Footer;真正用到哪些列,再去拉取对应列的元数据。这是宽表场景下性能提升的主要来源,在对象存储上尤为明显。
优化二:数值类型默认使用 Plain 编码
V3 将 INT、BIGINT 等数值类型的默认编码从 BitShuffle 切换为 PLAIN_ENCODING(原始二进制)。配合 LZ4 / ZSTD 压缩后,在大批量扫描时读取更快、CPU 开销更低。
优化三:字符串使用更紧凑的 Plain 编码
针对字符串和 JSONB,V3 引入 BINARY_PLAIN_ENCODING_V2,采用 [长度(varuint)][原始数据] 的流式布局,去掉了旧编码尾部需要的偏移表,存储更紧凑。
实测效果
测试条件:一张 7,000 列的宽表,共 10,000 个 Segment。

| 指标 | 旧格式 | V3 格式 | 提升幅度 |
|---|---|---|---|
| Segment 打开时间 | 65 s | 4 s | 快 16 倍 |
| 打开时内存占用 | 60 GB | < 1 GB | 降低 60 倍 |
如何启用
在建表语句的 PROPERTIES 中显式指定 storage_format 为 V3:
CREATE TABLE table_v3 (
id BIGINT,
name VARCHAR(128),
attrs VARIANT
)
DISTRIBUTED BY HASH(id) BUCKETS 32
PROPERTIES (
"storage_format" = "V3"
);
适用建议
- 建议启用:列数较多(几百及以上)的宽表、含
VARIANT列的表、部署在对象存储或分层存储上的表。 - 无需切换:列数较少(几十列以内)的普通表,旧格式已经足够。
相关文档
- 数据压缩:了解 V3 在数值与字符串类型上的编码与压缩协同优化。