首批通过分布式安全可靠测评,为关键业务系统打造
使用列存与行列混存
更新时间:2026-06-18 11:37:17
OceanBase 中的存储格式
OceanBase 采用 LSM-Tree 架构,用户数据整体分为基线数据和增量数据两部分:
- 基线数据:通过租户定期或按需选择的全局版本号,完成 Major Compaction 后生成;同一版本下所有副本的基线数据物理完全一致。根据建表时指定的存储模式,基线数据可以有行存、列存、行列混存三种形态。
- 增量数据:最新基线之后的所有写入(MemTable 内存数据或已转储的 SSTable)都属于增量数据,各副本独立维护,包含多版本数据。增量数据始终为行存。
基于上述基线/增量数据,行存、列存与行列混存在基线组织方式、写入与合并路径,以及查询时的访问特征上各不相同。下表从存储与写入特征、查询特征、典型场景和推荐方式等维度对三种格式作对比,便于按业务负载选型。
| 存储格式 | 存储与写入特征 | 查询特征 | 典型场景 | 推荐方式与说明 |
|---|---|---|---|---|
| 行存 | 基线按行存,所有列在微块内存储在一起;增量数据为行存;仅写行存 MemTable / 转储 | 适合点查与高并发 TP | 纯 OLTP,点查为主、无强分析需求 | 默认建表即为行存;若租户默认已改为列存或混存,需显式使用 WITH COLUMN GROUP(all columns) 创建行存表 |
| 列存 | 基线为列存:每列独立 SSTable,多列组成虚拟 SSTable;增量数据在写入和转储阶段仍按行存路径处理;写行存 MemTable / 转储,合并时与基线列存融合生成新列存基线 | 纯列存表点查通常不具行存优势,可通过行存索引优化;仅扫描涉及列,分析型查询优势明显 | OLAP 分析为主 | 建表可使用 WITH COLUMN GROUP(each column),或将租户 default_table_store_format 设为 column。行存基表 + 列存索引等组合参见 列存表与列存索引实践。 |
| 行列混存 | 同时存在行存基线与列存基线(冗余两份);增量数据为行存;写行存 MemTable / 转储,合并时同时维护行存与列存基线 | 点查使用行存副本;范围扫描通常更适合走列存路径,但最终仍由优化器基于代价选择 | HTAP:同一张表需要同时服务点查类访问与分析型查询,希望在统一存储组织下兼顾两类负载 | 使用 WITH COLUMN GROUP(all columns, each column),或将租户默认格式设为 compound;范围扫描默认走列存,点查回退行存。代价是双份基线以及更高的存储与合并资源开销;执行计划路径可结合统计信息与 Hint 调优 |
何时不宜仅凭数据量启用列存:数据量大但仍以高并发点查、短事务更新为主、且缺少大范围扫描与聚合需求时,默认行存通常更合适;若强行使用纯列存表,点查路径往往不占优,需额外依赖行存索引等设计。
关于列存的整体架构和核心特性,参见列存。
OceanBase 默认建表为行存;AP 业务需要列存或行列混存,可通过建表语句中的 WITH COLUMN GROUP、租户级 default_table_store_format,或在建表后通过 ALTER TABLE 增删列组完成转换。
如何创建列存表与行列混存表
方式一:建表时指定存储格式
通过 CREATE TABLE 的 WITH COLUMN GROUP 显式指定存储格式:
| 存储格式 | WITH COLUMN GROUP 写法 |
|---|---|
| 行存 | WITH COLUMN GROUP(all columns) |
| 列存 | WITH COLUMN GROUP(each column) |
| 行列混存 | WITH COLUMN GROUP(all columns, each column) |
当租户 default_table_store_format 不是 row 时,若需要行存表须显式写上 WITH COLUMN GROUP(all columns)。
方式二:设置租户配置项默认存储格式
通过租户级配置项 default_table_store_format 可指定该租户下未带 WITH COLUMN GROUP 的建表语句的默认格式:
row(默认):默认创建行存表。column:建表时自动加上WITH COLUMN GROUP(each column),即默认创建纯列存表。compound:建表时自动加上WITH COLUMN GROUP(all columns, each column),即默认创建行列混存表。
示例:
-- 设置租户下表的默认存储格式为列存
ALTER SYSTEM SET default_table_store_format = "column";
-- 设置租户下表的默认存储格式为行列混存
ALTER SYSTEM SET default_table_store_format = "compound";
该配置项仅对建表时未显式指定 WITH COLUMN GROUP 的语句生效,对索引表不生效。
行存与列存之间的转换
表创建后,可通过 ALTER TABLE 增删列组实现格式转换(语法参见 更改表(MySQL 模式)):
-- 示例:行存支付流水表转为纯列存
CREATE TABLE payment_ledger (
txn_id BIGINT NOT NULL,
order_id BIGINT NOT NULL,
pay_time DATETIME NOT NULL,
amount DECIMAL(18,2) NOT NULL,
PRIMARY KEY (txn_id, pay_time)
) WITH COLUMN GROUP(all columns);
ALTER TABLE payment_ledger ADD COLUMN GROUP(each column);
ALTER TABLE payment_ledger DROP COLUMN GROUP(all columns);
-- 纯列存 -> 行列混存
ALTER TABLE payment_ledger ADD COLUMN GROUP(all columns);
转换或大批量加载后建议执行合并并收集统计信息,再通过 EXPLAIN 确认分析查询是否走列存路径。
示例
下文以支付流水类业务为例,说明从选型到建表、验证的完整路径。
场景分析
以下用同一类业务字段(订单号、支付时间、渠道、状态、金额等)对比三种建表方式,便于理解 WITH COLUMN GROUP 的差异:
| 业务特征 | 建议格式 |
|---|---|
| 以按日/按渠道汇总金额、统计成功率等大范围扫描 + 聚合为主,很少按单号点查 | 纯列存表 WITH COLUMN GROUP(each column) |
既要按 order_id 高频点查单笔流水,又要对全表做分析报表 |
行列混存表 WITH COLUMN GROUP(all columns, each column) |
| 以单笔支付写入、状态更新为主,分析需求弱 | 行存表(默认或显式 WITH COLUMN GROUP(all columns)) |
建表
纯列存表
基线仅按列组织,适合只扫描少量列的聚合类 SQL(如按渠道汇总支付金额):
CREATE TABLE payment_ledger_cs (
txn_id BIGINT NOT NULL COMMENT '流水号',
order_id BIGINT NOT NULL COMMENT '订单号',
user_id BIGINT NOT NULL,
pay_time DATETIME NOT NULL,
pay_channel VARCHAR(32) NOT NULL COMMENT '支付渠道',
txn_status TINYINT NOT NULL COMMENT '0待支付 1成功 2失败',
amount DECIMAL(18,2) NOT NULL,
merchant_id BIGINT NOT NULL
)
PARTITION BY RANGE COLUMNS(pay_time) (
PARTITION p202501 VALUES LESS THAN ('2025-02-01 00:00:00'),
PARTITION p202502 VALUES LESS THAN ('2025-03-01 00:00:00')
)
WITH COLUMN GROUP(each column);
WITH COLUMN GROUP(each column) 表示基线按列存储;查询只读 pay_channel、amount 等少数列时,IO 与向量化执行更有利。
查询(导入并完成合并、收集统计信息后):
-- 按渠道汇总当日成功支付金额
SELECT pay_channel, SUM(amount) AS total_amt, COUNT(*) AS cnt
FROM payment_ledger_cs
WHERE pay_time >= '2025-01-15 00:00:00'
AND pay_time < '2025-01-16 00:00:00'
AND txn_status = 1
GROUP BY pay_channel;
若业务偶发需要按 order_id 点查,纯列存表通常不占优,可另建行存索引,参见 列存表与列存索引实践。
行列混存表
基线同时保留行存与列存两份组织,同一张表兼顾点查与分析:
CREATE TABLE payment_ledger_htap (
txn_id BIGINT NOT NULL,
order_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
pay_time DATETIME NOT NULL,
pay_channel VARCHAR(32) NOT NULL,
txn_status TINYINT NOT NULL,
amount DECIMAL(18,2) NOT NULL,
merchant_id BIGINT NOT NULL,
PRIMARY KEY (order_id, pay_time, txn_id)
)
PARTITION BY HASH(order_id) PARTITIONS 64
WITH COLUMN GROUP(all columns, each column);
WITH COLUMN GROUP(all columns, each column) 同时声明行存列组与列存列组,基线冗余两份;优化器可按代价在点查走行存路径、在宽范围扫描走列存路径。
查询:
-- TP:按订单号查询最近一笔成功支付(倾向走行存路径)
SELECT txn_id, pay_time, amount, pay_channel
FROM payment_ledger_htap
WHERE order_id = 10086001 AND txn_status = 1
ORDER BY pay_time DESC
LIMIT 1;
-- AP:按商户统计近 7 日支付总额(倾向走列存路径)
SELECT merchant_id, DATE(pay_time) AS dt, SUM(amount) AS daily_amt
FROM payment_ledger_htap
WHERE pay_time >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)
AND txn_status = 1
GROUP BY merchant_id, DATE(pay_time);
验证
写入样本数据并完成合并、统计信息收集后,对分析类 SQL 执行 EXPLAIN,可观察是否出现 COLUMN TABLE FULL SCAN 等列存相关算子(以当前版本执行计划输出为准):
EXPLAIN SELECT pay_channel, SUM(amount) FROM payment_ledger_cs
WHERE pay_time >= '2025-01-01' GROUP BY pay_channel;
使用列存表时的注意事项
- 合并与统计信息:批量导入或大规模数据就绪后,建议执行合并并收集统计信息,以改善读取性能并便于优化器生成有效执行计划。列存表数据量较大时,合并耗时可能相对行存更长。
- 更新与合并:列存表若存在大量更新且未及时合并,查询性能会受影响;批量导入后做一次合并可获得较优查询性能。
- 列存与列存副本:本文所述列存表指表在 F/R 副本上的存储格式。若使用独立列存副本(C 副本)承载 AP,请参考 AP 部署概述。