首批通过分布式安全可靠测评,为关键业务系统打造
列存表与列存索引实践
更新时间:2026-07-14 14:32:08
本篇文章介绍 OceanBase 中列存表、列存索引及其与行存表、行存索引的组合方案,并结合支付流水场景给出可落地的建表、建索引与查询示例,帮助将选型结论落实到具体 SQL。
列存表与列存索引的概念
列存表:主表基线以列存格式存储;创建方式为
WITH COLUMN GROUP(each column)(纯列存),或WITH COLUMN GROUP(all columns, each column)(行列混存)。详见 列存概述 — 如何创建列存表与行列混存表。列存索引:索引表本身以列存格式存储,而不是“在列存表上再建一种特殊索引类型”。创建索引时通过
WITH COLUMN GROUP(each column)指定列存索引;索引表同样可指定行存或列存列组。行存索引:索引表按行存组织(默认或
WITH COLUMN GROUP(all columns)),适合点查与覆盖索引回表路径。
下文示例均围绕同一张业务宽表 payment_ledger(支付流水):字段含 order_id、pay_time、pay_channel、txn_status、amount 等,便于对比不同组合下的读写特征。
场景组合
| 场景 | 适用业务 | 策略 | 优点 | 缺点 |
|---|---|---|---|---|
| 行存表 + 列存索引 | 以事务型业务为主,同时存在一定的分析型查询需求,且表为大宽表。 | 创建列存索引来提升分析类查询性能。 | 只对部分列进行冗余存储,且是列存格式。 | 需要根据查询语句的情况识别合适的字段创建列存索引,且数据写入时需要维护索引表数据,写入性能相对差一点。 |
| 列存表 + 行存索引 | 以分析型业务为主,但需支持高效的简单查询(如点查)。 | 创建索引时为了避免回表可以创建覆盖索引。 | 只冗余存储部分列数据,目前不支持在线将列存表转换为行存表或行列冗余表,可以通过添加行存索引来达到优化查询的目的。 | 需要根据查询语句的情况识别合适的字段创建行存索引,且数据写入时需要维护索引表数据,写入性能相对差一点。 |
| 行列混存表 | 需要同时兼顾事务型和分析型查询,且查询方式多样化。 | 可以直接冗余存储两份基线数据,让系统根据查询特点生成合适的执行计划,查询逻辑默认范围扫描走列存模式, 点查回退到行存模式。 | 不需要分析业务的查询特点, 分析类查询结果是强一致的。 | 会占用更多的磁盘空间,且执行计划容易出现不准的情况。 |
| 纯列存表(无索引) | AP 场景 | 使用 WITH COLUMN GROUP(each column) 建列存表,列式存储数据 |
大批量导入与宽表少列扫描效率高 | 按主键/订单号点查慢 |
示例
示例一:行存表 + 列存索引
支付核心链路是 INSERT 与按 order_id 查状态;运营报表按渠道、日期对 amount 做 SUM/COUNT,表列数多但分析只扫少数列。
建表与列存索引:
CREATE TABLE payment_ledger (
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,
remark VARCHAR(256) DEFAULT NULL,
PRIMARY KEY (order_id, pay_time, txn_id)
)
PARTITION BY HASH(order_id) PARTITIONS 64
WITH COLUMN GROUP(all columns);
-- 列存索引:冗余分析常用列,避免宽表全列扫描
CREATE INDEX idx_pay_analytics
ON payment_ledger (pay_time, pay_channel, txn_status, amount)
WITH COLUMN GROUP(each column);
典型 TP 查询(走主表行存 + 主键):
SELECT txn_id, txn_status, amount, pay_channel
FROM payment_ledger
WHERE order_id = 10086001
ORDER BY pay_time DESC
LIMIT 1;
典型分析查询(优化器可能选择列存索引,仅扫描索引中的列):
SELECT pay_channel,
SUM(amount) AS succ_amt,
COUNT(*) AS succ_cnt
FROM payment_ledger
WHERE pay_time >= '2025-01-01'
AND pay_time < '2025-02-01'
AND txn_status = 1
GROUP BY pay_channel;
主表保持行存,不影响高并发写入与点查;分析负载由列存索引承接,相比把整张表改成纯列存,对 TP 更稳妥。索引列应覆盖过滤与聚合所需字段,避免回表扫宽行。
示例二:列存表 + 行存索引(分析为主,补点查)
流水已以批量导入/对账为主,日常 SQL 多为全分区扫描聚合;仅客服、风控等场景按 order_id 查单笔。
建表与行存覆盖索引:
CREATE TABLE payment_ledger_cs (
txn_id BIGINT NOT NULL,
order_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 (txn_id, pay_time)
)
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(all columns, each column);
-- 行存索引 + STORING:点查订单时避免回主表宽行
CREATE INDEX idx_order_lookup
ON payment_ledger_cs (order_id)
STORING (txn_status, amount, pay_time, pay_channel)
WITH COLUMN GROUP(all columns);
分析查询(主表列存):
SELECT merchant_id, SUM(amount) AS total
FROM payment_ledger_cs
WHERE pay_time >= '2025-01-01' AND txn_status = 1
GROUP BY merchant_id;
点查查询(行存索引):
SELECT txn_status, amount, pay_time, pay_channel
FROM payment_ledger_cs
WHERE order_id = 10086001
ORDER BY pay_time DESC
LIMIT 5;
主表 each column 保证汇总类 SQL 的列存扫描优势;点查由行存索引承担。
示例三:纯列存表(大范围分析,弱 TP)
表仅服务日终对账、监管报送等批处理,无在线按单号查询,且可接受无主键表以换取导入性能。更多信息,参见 AP 场景下的主键设计实践)。
CREATE TABLE payment_ledger_dw (
txn_id BIGINT,
order_id BIGINT,
pay_time DATETIME,
pay_channel VARCHAR(32),
txn_status TINYINT,
amount DECIMAL(18,2),
merchant_id BIGINT
)
WITH COLUMN GROUP(each column);
-- 批量导入后建议合并并收集统计信息
SELECT DATE(pay_time) AS dt,
COUNT(*) AS txn_cnt,
SUM(CASE WHEN txn_status = 1 THEN amount ELSE 0 END) AS succ_amt
FROM payment_ledger_dw
WHERE pay_time >= '2025-01-01' AND pay_time < '2025-02-01'
GROUP BY DATE(pay_time);
示例四:行列混存表
业务坚持一张 payment_ledger 同时承接支付查询与运营分析,且可接受双份基线存储与更高合并成本。
CREATE TABLE payment_ledger_htap (
txn_id BIGINT NOT NULL,
order_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);
-- 点查类(优化器倾向行存路径)
SELECT amount, txn_status FROM payment_ledger_htap
WHERE order_id = 10086001 AND txn_status = 1
ORDER BY pay_time DESC LIMIT 1;
-- 分析类(优化器倾向列存路径)
SELECT pay_channel, SUM(amount) FROM payment_ledger_htap
WHERE pay_time >= '2025-01-15' AND txn_status = 1
GROUP BY pay_channel;