仓模式列存表(Warehouse Column Store Table)是 OLAP 资源组的内置分析型数据表,采用列式存储与 MPP 分布式执行架构,面向大规模离线与实时分析场景设计。仓模式列存表的数据完全托管于 OLAP 资源组内部,与湖模式列存数据(lindorm_columnar)和宽表引擎数据(lindorm_table)相互独立,具备完整的 DDL/DML 能力和高性能 OLAP 计算能力。
连接 OLAP 资源组后,通过以下语句切换至仓模式列存表数据源:
SET CATALOG default_catalog;表类型
仓模式列存表根据写入模型的不同分为两种类型:明细表和主键表。表类型在建表时确定,建表后不可修改。 数据导入时,系统依据表类型对导入数据进行相应的排序、处理和存储。
明细表
明细表(Duplicate Key Table)是仓模式列存表的默认表类型,采用追加写入(Append-Only)数据模型。数据写入时系统不做任何去重或聚合处理,允许排序键相同的数据行并存,完整保留每一条导入记录。
适用场景
原始日志、行为事件、埋点数据等不需要更新的明细流水分析
同一数据集需要从多个维度灵活聚合,不希望受预聚合结构限制
需要保留完整历史快照,按任意条件精确召回原始数据
不适用场景:如果业务数据存在频繁的更新或删除需求(如订单状态变更、用户信息修改),应使用主键表。
建表语法
-- 示例:用户行为事件明细表
-- 按 (event_day, event_type) 排序:加速按日期和事件类型过滤的查询
-- 按 user_id 哈希分桶:保证同一用户的数据集中在同一分桶,加速用户维度 JOIN
CREATE TABLE user_event_log (
event_day DATE NOT NULL COMMENT "事件日期",
event_type TINYINT NOT NULL COMMENT "事件类型(1=点击 2=曝光 3=转化)",
user_id BIGINT NOT NULL COMMENT "用户ID",
item_id BIGINT COMMENT "内容/商品ID",
session_id VARCHAR(64) COMMENT "会话ID",
duration_ms INT COMMENT "停留时长(毫秒)",
province VARCHAR(32) COMMENT "省份",
channel VARCHAR(32) COMMENT "来源渠道"
)
DUPLICATE KEY(event_day, event_type)
PARTITION BY date_trunc('day', event_day)
DISTRIBUTED BY HASH(user_id);注意事项
支持通过
ORDER BY替代DUPLICATE KEY指定排序键,语义等价;若两者同时出现,DUPLICATE KEY不生效。使用ORDER BY时,该子句须放在DISTRIBUTED BY之后。若排序键和分桶键均未指定,系统默认取前三列作为排序键。
明细表支持为所有列创建 Bitmap 索引、Bloom Filter 索引,无需区分 Key/Value 列。
明细表不支持
UPDATE和DELETEDML 语句(主键表支持)。如果导入两行完全相同的数据,明细表会将其视为独立的两行存储,不会自动去重。
数据模型
明细表将所有写入数据按排序键进行物理排序后存储于列式文件中。每条记录独立存在,写入后不可原地修改。数据文件在后台异步进行 Compaction(合并小文件、回收空间),但不会对数据内容做任何去重或变更。
这种模型的核心优势在于写入吞吐高、灵活性强:写入路径没有 Merge 或 Dedup 开销,适合高频追加场景;查询侧可以按任意维度自由聚合,不受预定义聚合结构的约束。
排序键设计
通过 DUPLICATE KEY 或 ORDER BY 声明排序键,排序键决定数据的物理存储顺序,同时作为前缀索引(Shortkey Index)的构建依据。
排序键设计原则:
将最高频的过滤列排在最前面:前缀索引只对排序键的连续前缀有效,最频繁过滤的列应排在第一位。
优先使用低基数列:低基数列(如日期、事件类型、城市)作为排序键前缀,可使相同值的行聚集存储,配合 ZoneMap 索引大幅减少 I/O。
高基数列(如 user_id、device_id)不适合置于排序键前缀:这类列值离散,排序键过滤效果差,建议用作 Bloom Filter 索引列或分桶键。
排序键列数建议不超过 3 列:超过 36 字节后前缀索引不再扩展,过多列对性能提升有限。
主键表
主键表(Primary Key Table)对主键列强制唯一非空约束,是专为实时数据更新与高性能即席查询并重场景设计的存储模型。
适用场景
通过 CDC 工具(如 Flink CDC)实时同步 MySQL、PostgreSQL 等事务型数据库的全量增删改变更
电商订单状态追踪、用户画像实时更新、物流运单状态同步等高频 UPSERT 场景
多路数据流合并写入同一张宽表,不同数据源分别更新各自负责的列(部分列更新)
需要对最新快照数据进行高并发复杂分析查询
建表语法
-- 示例:电商订单实时分析表
-- 主键列 order_id、dt 须定义在最前面;分区列 dt 和分桶列 order_id 均须包含在主键中
-- ORDER BY (dt, merchant_id) 加速按日期和商户维度的范围分析
-- 开启持久化主键索引,避免大数据量下主键索引占满内存
CREATE TABLE orders (
order_id BIGINT NOT NULL COMMENT "订单ID",
dt DATE NOT NULL COMMENT "下单日期",
merchant_id INT NOT NULL COMMENT "商户ID",
user_id BIGINT NOT NULL COMMENT "用户ID",
good_id INT NOT NULL COMMENT "商品ID",
good_name VARCHAR(128) NOT NULL COMMENT "商品名称",
total_amount DECIMAL(18, 2) NOT NULL COMMENT "订单金额",
payment_amount DECIMAL(18, 2) COMMENT "实付金额",
status TINYINT NOT NULL COMMENT "订单状态",
create_time DATETIME NOT NULL COMMENT "创建时间",
update_time DATETIME COMMENT "最后更新时间"
)
PRIMARY KEY (order_id, dt)
PARTITION BY date_trunc('day', dt)
DISTRIBUTED BY HASH(order_id)
ORDER BY (dt, merchant_id);注意事项
主键列具有唯一非空约束;主键列不支持
DECIMAL类型;主键总长度不得超过 128 字节。建表时主键列必须连续置于列定义最前面:主键列须排在
CREATE TABLE所有列定义的最前面,且主键列之间不能穿插非主键列,否则建表报错。使用分区和哈希分桶时,主键必须包含分区列和分桶列,以保证系统能精确定位目标分区和分桶执行更新。
主键列的值不可更新:
UPDATE只能修改非主键列;若必须变更主键值,须先DELETE再INSERT。排序键(
ORDER BY)与主键完全解耦,可独立设计为任意列组合;排序键支持建表后修改(ALTER TABLE tbl ORDER BY ...),但不支持删除排序键,也不支持修改排序列的数据类型。主键表支持完整 DML:
INSERT、UPDATE(支持按 Key 列或 Value 列过滤)、DELETE(支持按 Key 列或 Value 列过滤)。主键表仅支持哈希分桶,不支持随机分桶。
写入机制:Delete+Insert
主键表采用 Delete+Insert 写入策略,区别于传统的 Merge-On-Read(读时合并)模型:
写入时:系统通过主键索引定位旧数据行,将其标记删除,再写入新数据行,同时更新主键索引。
查询时:数据已处于最终状态,无需在线合并多版本,查询直接读取有效行。
这一机制使得主键表的查询性能显著优于 Merge-On-Read 模型,尤其在高更新频率场景下,读取放大问题被彻底消除。
主键设计原则
主键应唯一标识业务实体:选取能全局唯一确定一条记录的字段组合,如
order_id、(user_id, event_id)。主键必须包含分区列和分桶列:使用分区(
PARTITION BY)和哈希分桶(DISTRIBUTED BY HASH)时,主键须涵盖分区列和分桶列,以保证系统能精确定位目标分区和分桶执行更新。保持主键精简:主键总字节长度不得超过 128 字节,列数建议不超过 3 列。优先选用
INT、BIGINT等占用内存小的类型,避免使用VARCHAR;主键列过多会增加索引存储和维护开销。主键列不支持
DECIMAL类型:可使用BIGINT、VARCHAR等常见类型。主键列不可为 NULL:所有主键列均须声明
NOT NULL。
排序键与主键解耦
主键(PRIMARY KEY)定义唯一性约束,排序键(ORDER BY)定义数据的物理存储顺序,两者完全解耦,可独立设计。
排序键列可以是任意列的排列组合,不要求包含主键列,以查询最频繁的过滤列为设计依据。
若不指定
ORDER BY,系统默认使用主键列作为排序键。排序键支持建表后修改:
ALTER TABLE tbl ORDER BY (col1, col2, ...);但不支持删除排序键,也不支持修改排序列的数据类型。
部分列更新
主键表支持部分列更新(Partial Column Update)模式,允许不同写入方只携带需要更新的列,系统以主键为关联键自动合并各列数据。这是多路数据流实时写宽表的核心能力。
-- 示例:启用部分列更新,写入时只更新 status 和 update_time 列
INSERT INTO orders (order_id, status, update_time)
VALUES (10001, 3, '2024-03-01 10:30:00');
-- 其余列(如 total_amount、user_id 等)保持原值不变数据分布
数据分布策略决定数据在集群中的物理存储方式,合理设计分区和分桶策略,可实现查询时的有效数据裁剪和集群计算资源的充分利用。
仓模式列存表的数据分布采用两级结构:
表(Table)
└── 分区(Partition) ← 逻辑划分,用于数据管理和查询裁剪
└── 分桶(Bucket) ← 物理划分,用于均匀分布和并行计算
分区
分区将表按分区键拆分为独立的数据管理单元。查询条件包含分区键时,引擎自动定位并仅扫描目标分区(分区裁剪),跳过无关分区,显著减少 I/O。分区也是数据生命周期管理的基本单元,可通过 DROP PARTITION 快速删除历史分区,效率远高于 DELETE 语句。
表达式分区(推荐)
基于 date_trunc() 函数表达式按时间粒度自动建分区,无需预定义分区列表,系统按数据中的时间字段动态创建,推荐用于时序数据场景。
-- 按天自动分区
PARTITION BY date_trunc('day', event_time)
-- 按月自动分区
PARTITION BY date_trunc('month', order_date)Range 分区
按数值或日期列的连续区间划分,适合时序数据按日/周/月管理。
PARTITION BY RANGE(event_day) (
PARTITION p202401 VALUES LESS THAN ("2024-02-01"),
PARTITION p202402 VALUES LESS THAN ("2024-03-01"),
PARTITION p202403 VALUES LESS THAN ("2024-04-01")
)List 分区
按列的离散枚举值划分,适合按地域、状态等维度管理数据。
PARTITION BY LIST(region) (
PARTITION p_east VALUES IN ("shanghai", "jiangsu", "zhejiang"),
PARTITION p_north VALUES IN ("beijing", "tianjin", "hebei"),
PARTITION p_south VALUES IN ("guangdong", "fujian", "hainan")
)注意 分区键仅支持日期类型(DATE、DATETIME)和整数类型(INT、BIGINT等)。
分桶
分桶在分区内部将数据进一步均匀打散到多个物理存储单元(Bucket/Tablet),提升并行扫描能力。
哈希分桶
按指定分桶键的哈希值分配数据,相同分桶键的数据落入同一桶,有利于等值查询和同键 Join(本地化 Join 零网络传输)。分桶键应选择高基数且查询最频繁的过滤/关联列,避免数据倾斜。
-- 系统自动推算分桶数
DISTRIBUTED BY HASH(user_id)
-- 手动指定分桶数
DISTRIBUTED BY HASH(order_id) BUCKETS 32随机分桶
数据随机分配至各桶,无需指定分桶键,写入均匀,适合无固定查询模式的明细写入场景。明细表默认使用随机分桶,主键表不支持随机分桶。
DISTRIBUTED BY RANDOM最佳实践 单个 Bucket 的数据量建议控制在 100 MB ~ 1 GB 之间。Bucket 过大导致并行度不足;Bucket 过多增加元数据调度开销。避免过细的分区(如按小时),大量小分区会显著增加元数据压力。
综合示例
-- 充值明细表:按天表达式分区,按 user_id 哈希分桶
CREATE TABLE recharge_detail (
id BIGINT NOT NULL COMMENT "流水ID",
user_id BIGINT NOT NULL COMMENT "用户ID",
recharge_money DECIMAL(18, 2) NOT NULL COMMENT "充值金额",
city VARCHAR(32) NOT NULL COMMENT "城市",
dt DATE NOT NULL COMMENT "充值日期"
)
DUPLICATE KEY(id)
PARTITION BY date_trunc('day', dt)
DISTRIBUTED BY HASH(user_id);数据类型
数值类型
类型 | 字节 | 取值范围 |
BOOLEAN | 1 |
|
TINYINT | 1 | -128 ~ 127 |
SMALLINT | 2 | -32,768 ~ 32,767 |
INT | 4 | -2,147,483,648 ~ 2,147,483,647 |
BIGINT | 8 | -2⁶³ ~ 2⁶³-1 |
LARGEINT | 16 | -2¹²⁷ ~ 2¹²⁷-1,适用于超大整数(如 128 位设备ID) |
FLOAT | 4 | 单精度浮点,近似值 |
DOUBLE | 8 | 双精度浮点,近似值 |
DECIMAL(M, D) | 可变 | 精确小数,M 为总位数(最大 38),D 为小数位数,推荐金融/交易场景 |
注意 数值类型不支持 UNSIGNED 修饰符。字符串类型
类型 | 最大长度 | 说明 |
CHAR(N) | 255 字节 | 定长字符串,不足长度以空格补齐 |
VARCHAR(N) | 1,048,576 字节 | 变长字符串,按实际长度存储 |
STRING | 同 VARCHAR | 等同于大容量 VARCHAR,适合非结构化文本 |
日期时间类型
类型 | 说明 |
DATE | 日期,格式 |
DATETIME | 日期时间,格式 |
OLAP 专有类型
类型 | 说明 |
BITMAP | 位图类型,配合 |
HLL | HyperLogLog 类型,配合 |
BITMAP 和 HLL 类型不能用作排序键列或主键列。
半结构化类型
类型 | 说明 |
JSON | 弹性 JSON 文档,支持路径查询( |
ARRAY<T> | 同类型元素数组,支持 |
MAP<K, V> | 键值对映射,适合存储动态属性、标签等 Schema 不固定的数据 |
STRUCT<field: type> | 嵌套结构体,支持按字段名访问,适合多级嵌套数据建模 |
计算特性
SQL 分析算子
仓模式列存表支持标准 SQL 分析语法,全面覆盖 OLAP 场景下的分析计算需求。
投影与过滤
SELECT 支持对指定列进行投影,列名可设置别名(AS)、参与算术表达式或函数计算。WHERE 子句支持多条件组合谓词:
谓词类型 | 示例 |
等值过滤 |
|
范围过滤 |
|
集合过滤 |
|
模糊匹配 |
|
NULL 判断 |
|
正则过滤 |
|
谓词在执行时尽可能下推至存储层,减少数据读取量。
连接查询(JOIN)
支持以下连接类型:
JOIN 类型 | 说明 |
| 返回两表的匹配行 |
| 保留左表所有行,右表无匹配则填 NULL |
| 保留右表所有行,左表无匹配则填 NULL |
| 保留两表所有行 |
| 笛卡尔积 |
| 仅返回左表中在右表有匹配的行,不返回右表列 |
| 仅返回左表中在右表无匹配的行 |
执行层根据表大小和数据分布自动选择最优 Join 策略:小表广播至所有节点(Broadcast Join)、数据按 Join 键重分布(Shuffle Join),或当分桶键与 Join 键一致时执行本地化 Join(零网络传输)。
-- 示例:订单明细与用户画像 JOIN
SELECT
o.order_id,
o.total_amount,
u.user_level,
u.city
FROM orders o
INNER JOIN user_profile u ON o.user_id = u.user_id
WHERE o.dt >= '2024-01-01'
AND u.user_level IN ('gold', 'platinum');集合运算
运算符 | 说明 |
| 合并多个查询结果,保留重复行,性能更优 |
| 合并多个查询结果并去重 |
| 返回两个结果集的交集 |
| 返回在第一个结果集中但不在第二个中的行 |
分组聚合(GROUP BY)
支持标准聚合函数(COUNT、SUM、AVG、MIN、MAX、COUNT DISTINCT)以及通过 HAVING 对聚合结果二次过滤。
支持以下扩展聚合语法,一次扫描完成多维度汇总,性能显著优于多次独立聚合查询:
-- GROUPING SETS:显式指定多组维度组合
SELECT city, dt, SUM(amount) AS total
FROM orders
GROUP BY GROUPING SETS ((city, dt), (city), ());
-- ROLLUP:层级汇总(从细粒度到总计)
-- 等价于 GROUPING SETS ((city, dt), (city), ())
SELECT city, dt, SUM(amount) AS total
FROM orders
GROUP BY ROLLUP (city, dt);
-- CUBE:对所有维度的全量组合汇总
-- 等价于 GROUPING SETS ((city, dt), (city), (dt), ())
SELECT city, dt, SUM(amount) AS total
FROM orders
GROUP BY CUBE (city, dt);排序与分页
支持 ORDER BY 多列排序(ASC / DESC),配合 LIMIT 和 OFFSET 实现分页查询。
SELECT order_id, total_amount
FROM orders
WHERE dt = '2024-03-01'
ORDER BY total_amount DESC, order_id ASC
LIMIT 20 OFFSET 40;子查询
支持以下子查询形式,优化器自动将可转换的子查询改写为等价 JOIN 执行:
子查询类型 | 示例 |
标量子查询 |
|
|
|
|
|
相关子查询 | 子查询引用外层查询的列,逐行计算 |
窗口函数(Window Function)
窗口函数在保留原始行的同时对数据分组计算,是 OLAP 分析的核心能力。通过 OVER (PARTITION BY ... ORDER BY ... ROWS/RANGE ...) 语法定义计算窗口。
函数类别 | 常用函数 |
排名函数 |
|
聚合分析 |
|
偏移访问 |
|
首尾取值 |
|
-- 示例:计算每个用户的累计消费金额与当日消费排名
SELECT
user_id,
order_id,
dt,
total_amount,
SUM(total_amount) OVER (
PARTITION BY user_id
ORDER BY dt
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS cumulative_amount,
RANK() OVER (
PARTITION BY dt
ORDER BY total_amount DESC
) AS daily_rank
FROM orders;
-- 示例:计算用户相邻两次购买的间隔天数
SELECT
user_id,
order_id,
dt,
DATEDIFF(dt, LAG(dt, 1) OVER (PARTITION BY user_id ORDER BY dt)) AS days_since_last_order
FROM orders;
CASE WHEN 条件表达式
支持搜索式和简单式两种语法,可在 SELECT、WHERE、ORDER BY、聚合函数等任意位置使用。
SELECT
order_id,
total_amount,
CASE
WHEN total_amount >= 10000 THEN '大额'
WHEN total_amount >= 1000 THEN '中额'
WHEN total_amount >= 100 THEN '小额'
ELSE '微额'
END AS amount_tier,
SUM(CASE WHEN status = 1 THEN total_amount ELSE 0 END) AS paid_amount
FROM orders
GROUP BY order_id, total_amount;索引
仓模式列存表内置多层索引体系,各类索引协同工作,在不同查询场景下发挥过滤和加速效果。
前缀索引(Shortkey Index)
系统自动创建,每 1024 行取排序键前缀(不超过 36 字节、不超过 3 列)存储一条稀疏索引项。查询中带有排序键前缀过滤条件时,引擎通过二分查找快速定位目标数据块,无需手动创建。
触发条件:查询过滤条件必须能构成排序键的前缀。
排序键:(event_day, site_id, city_code)
✅ WHERE event_day = '2024-01-01' -- 命中前缀索引
✅ WHERE event_day = '2024-01-01' AND site_id = 10 -- 命中前缀索引
❌ WHERE city_code = 'BJ' -- 不构成前缀,无法使用
建表时应将查询最高频的过滤列排在排序键最前面,使其优先被前缀索引覆盖。
ZoneMap 索引
系统自动为每列的每个数据块(Page,默认 64 KB)维护最小值(Min)、最大值(Max)和 NULL 存在性统计,无需手动创建。执行范围查询时,引擎通过比对各 Page 的 Min/Max 快速跳过不满足条件的 Page,实现数据块级别的高效裁剪。
适用场景:数值、日期等有序类型列上的范围过滤(BETWEEN、>、<、>=、<=)。
Bloom Filter 索引
基于 Bloom Filter 算法,以数据块(Page)为粒度构建跳数索引。写入时对每个 Page 中的列值计算哈希写入 Bloom Filter;查询时对过滤条件值计算哈希并判断是否存在,不存在则直接跳过该 Page,从而减少 I/O。
适用场景:高基数列(如用户ID、订单ID、设备码)的等值查询(=、IN)。此类列基数过高,不适合 Bitmap 索引(存储开销过大)。
通过 PROPERTIES 中的 bloom_filter_columns 参数指定,建表后支持通过 ALTER TABLE 动态变更。
-- 建表时创建
CREATE TABLE orders (
order_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
merchant_id INT NOT NULL,
dt DATE NOT NULL,
amount DECIMAL(18, 2)
)
PRIMARY KEY(order_id)
DISTRIBUTED BY HASH(order_id)
PROPERTIES (
"bloom_filter_columns" = "user_id, merchant_id"
);
-- 建表后动态添加
ALTER TABLE orders SET ("bloom_filter_columns" = "user_id, merchant_id, dt");
-- 删除 Bloom Filter 索引
ALTER TABLE orders SET ("bloom_filter_columns" = "");注意事项:
不支持
TINYINT、FLOAT、DOUBLE、DECIMAL类型列。仅对等值查询有效,范围查询(
>、<、BETWEEN)无效。不适用于低基数列(应使用 Bitmap 索引)。
Bitmap 索引
为列的每个唯一值建立一个位图(每位对应表中一行,0/1 表示该行是否含有该值)。多条件查询时,通过对多个位图执行位运算(AND/OR/NOT)高效得到结果集,内部使用 Roaring Bitmap 压缩结构,存储空间相较传统位图最高节省 90%。
适用场景:中等基数列(建议基数在 10,000 ~ 100,000 之间)的等值查询,以及多列组合过滤(如用户画像中同时按年龄段、消费等级、城市过滤)。
通过 INDEX 子句定义,或建表后通过 CREATE INDEX 动态添加(属于异步 Schema Change 操作)。
-- 建表时创建 Bitmap 索引
CREATE TABLE user_profile (
user_id BIGINT NOT NULL COMMENT "用户ID",
age_range TINYINT COMMENT "年龄段(1-5)",
gender TINYINT COMMENT "性别(0=女,1=男)",
city_code VARCHAR(20) COMMENT "城市编码",
user_level TINYINT COMMENT "用户等级(1-5)",
register_day DATE COMMENT "注册日期",
INDEX idx_age_range (age_range) USING BITMAP COMMENT "年龄段索引",
INDEX idx_city_code (city_code) USING BITMAP COMMENT "城市索引",
INDEX idx_user_level (user_level) USING BITMAP COMMENT "等级索引"
)
DUPLICATE KEY(user_id)
DISTRIBUTED BY HASH(user_id);
-- 建表后动态添加
CREATE INDEX idx_gender ON user_profile (gender) USING BITMAP;
-- 查看索引
SHOW INDEX FROM user_profile;
-- 删除索引
DROP INDEX idx_gender ON user_profile;注意事项:
不支持
FLOAT、DOUBLE、BOOLEAN、DECIMAL类型列。基数过高(超过 100 万)时,Bitmap 索引存储膨胀,应改用 Bloom Filter 索引。
若索引过滤比例低(如筛选超过 50% 的数据),索引收益为负,不建议创建。
索引选型参考
查询场景 | 推荐索引 |
排序键前缀列的等值/范围查询 | 前缀索引(自动,无需创建) |
数值/日期列的范围过滤 | ZoneMap 索引(自动,无需创建) |
中等基数列的等值过滤、多列组合过滤 | Bitmap 索引 |
高基数列(ID类)的等值过滤 | Bloom Filter 索引 |