好的,我们来详细解释 Doris 中主键模型的 读时合并(Read-Computing Merge) 模式。这是一种高效处理数据更新的机制,特别适用于需要支持主键更新且对查询性能要求高的场景。
在 1.2 版本前,Doris 中的主键模型默认使用读时合并模式,数据在写入时并不进行合并,以增量的方式被追加存储,在 Doris 内保留多个版本。查询 时,会对数据进行相同 Key 的版本合并。读时合并适合写多读少的场景,在查询是需要进行多个版本合并。
📌 核心概念
在 Doris 的主键模型中,读时合并模式的核心思想是:
- 写入时分离:当新数据写入时,系统并不立即覆盖旧数据,而是将新数据存储在增量存储区(如 Rowset Delta)。
- 查询时合并:当查询请求到达时,系统在内存中动态地将基础数据(Base)与增量数据(Delta)进行合并,生成最新的数据视图供查询使用。
🔧 工作原理
- 数据写入流程
假设原始数据为:
| 主键 (id) | 列 A | 列 B |
|---|---|---|
| 1 | A1 | B1 |
| 2 | A2 | B2 |
当新数据写入(如更新 id=1):
UPDATE table SET colA = 'A1_new' WHERE id = 1;
系统会将更新记录写入增量存储区:
| 主键 (id) | 列 A | 列 B | 版本号 (version) |
|---|---|---|---|
| 1 | A1_new | NULL | 2 |
注:未更新的列(如
colB)通常标记为NULL以节省空间。
- 查询合并流程
当查询请求到达时(如 SELECT * FROM table WHERE id = 1),系统执行以下合并逻辑:
- 定位数据 :在基础数据中找到
id=1的记录(A1, B1)。 - 合并增量 :在增量存储区找到最新版本(如
version=2)的记录(A1_new, NULL)。 - 动态合并 :生成最终结果:
\\text{colA} = \\text{A1_new}, \\quad \\text{colB} = \\text{B1}
⚡ 性能优势
-
写入高效
- 增量写入避免全量覆盖,减少 I/O 压力。
- 支持高并发写入(如 Kafka 流式写入)。
-
查询优化
-
通过向量化计算加速合并过程(示例伪代码):
def mergeRow(base: Row, delta: Row): Row = { val merged = new Row() for (column in columns) { merged(column) = delta.getOrNull(column) ?? base(column) } merged } -
利用索引快速定位数据(如主键索引)。
-
-
空间与时效平衡
- 后台异步合并任务(Compaction)定期将增量数据合并到基础数据中,避免增量数据无限增长。
🚫 使用限制
- 内存压力:大量增量数据可能导致查询时内存占用升高。
- 版本管理:需合理设计版本清理策略(如按时间保留最近 N 个版本)。
💎 总结
读时合并模式通过写入轻量化 + 查询时动态计算,在保证主键唯一性的同时,显著提升了高并发更新场景下的系统吞吐量。其核心价值在于: $$ \text{性能} \propto \frac{\text{写入效率}}{\text{查询延迟}} $$ 适合实时数仓、频繁更新的业务表(如用户画像、交易订单)等场景。