1. Doris增量更新机制概述
Apache Doris作为一款高性能的MPP分析型数据库,其增量更新机制是其区别于传统OLAP系统的核心能力之一。在实时数据分析场景中,增量更新能力直接决定了数据的新鲜度和业务决策的时效性。
Doris通过独特的表模型设计和高效的写入机制,实现了从传统T+1分析模式到秒级延迟的跨越。其增量更新机制主要围绕以下几个核心概念构建:
- 表模型与更新语义:Doris提供三种表模型(Unique Key、Aggregate Key、Duplicate Key),每种模型对应不同的更新行为
- 写入即更新:通过Stream Load等导入方式天然支持UPSERT语义
- 部分列更新:仅更新指定列,大幅减少I/O开销
- 删除标记:通过逻辑标记+后台清理机制实现高效删除
2. 核心表模型与更新机制
2.1 Unique Key模型:实时更新的核心
Unique Key模型是Doris实现高效增量更新的基石。其核心特点是:
sql复制CREATE TABLE user_profile (
user_id BIGINT,
name VARCHAR(50),
last_login DATETIME,
score DECIMAL(10,2)
) UNIQUE KEY(user_id)
DISTRIBUTED BY HASH(user_id) BUCKETS 10
PROPERTIES (
"enable_unique_key_merge_on_write" = "true"
);
在这种模型下,当导入数据的主键已存在时,新数据会自动覆盖旧数据。这种机制特别适合需要频繁更新的用户画像、订单状态等业务场景。
关键提示:从Doris 2.0开始,Merge-on-Write(MoW)成为Unique Key的默认实现方式,相比旧版的Merge-on-Read(MoR)有显著的查询性能优势。
2.2 Aggregate Key模型的更新特性
Aggregate Key模型通过预聚合方式支持特定形式的更新:
sql复制CREATE TABLE user_behavior (
user_id BIGINT,
date DATE,
page_views BIGINT SUM,
dwell_time BIGINT SUM
) AGGREGATE KEY(user_id, date)
DISTRIBUTED BY HASH(user_id) BUCKETS 10;
这种模型下,相同维度的数据会自动按照SUM、MAX等聚合函数合并,适用于指标累计型场景。但需要注意:
- 仅支持基于Key列的REPLACE更新
- 无法单独更新非聚合列
2.3 三种更新方式对比
| 更新方式 | Unique Key | Aggregate Key | Duplicate Key |
|---|---|---|---|
| 全列覆盖更新 | 支持 | 不支持 | 不支持 |
| 部分列更新 | 支持 | 不支持 | 不支持 |
| 条件更新(WHERE) | 支持 | 不支持 | 不支持 |
| 删除特定行 | 支持 | 支持(Key列) | 支持 |
| 更新延迟 | 秒级 | 秒级 | 秒级 |
3. 增量更新的技术实现
3.1 Merge-on-Write的存储机制
MoW机制的核心思想是将合并操作提前到写入阶段完成。当新数据写入时:
- 系统首先定位到包含相同主键的现有数据文件
- 在内存中进行新旧数据合并
- 生成新的数据版本
- 异步回收旧数据空间
这种机制虽然增加了写入时的CPU开销,但使得查询时无需额外的合并操作,显著提升了查询性能。
性能数据:在SSD存储环境下,MoW的99%查询延迟比MoR降低3-5倍,特别适合读多写少的分析场景。
3.2 部分列更新实现原理
部分列更新是Doris 2.0引入的重要特性,其工作流程如下:
- 客户端仅提交需要更新的列
- BE节点从磁盘读取该行其他列的当前值
- 在内存中补全整行数据
- 执行完整的行级更新
bash复制# 使用Stream Load进行部分列更新
curl --location-trusted -u user:passwd \
-H "partial_columns:true" \
-H "columns:id,name,age" \
-T data.csv http://fe_host:8030/api/db/tbl/_stream_load
实际经验:对于宽表(100+列)的频繁部分更新,建议使用行存模式(row-oriented)来减少随机IO。可通过
"store_row_column" = "true"参数开启。
3.3 删除操作的实现
Doris采用标记删除+后台压缩的删除策略:
- 删除操作会写入一个带有删除标记的特殊记录
- 查询时自动过滤被标记删除的记录
- 后台Compaction进程物理清理数据
sql复制-- 删除操作的两种方式
DELETE FROM tbl WHERE create_time < '2023-01-01'; -- 标准SQL方式
-- 通过Stream Load进行批量删除
echo '{"user_id":123, "__DORIS_DELETE_SIGN__":1}' > delete.json
curl ... -T delete.json http://fe_host:8030/api/db/tbl/_stream_load
4. 典型应用场景与优化
4.1 实时数仓同步
通过Flink CDC实现MySQL到Doris的实时同步:
sql复制-- 创建目标表
CREATE TABLE order_cdc (
order_id BIGINT,
status VARCHAR(20),
amount DECIMAL(10,2),
update_time DATETIME
) UNIQUE KEY(order_id)
DISTRIBUTED BY HASH(order_id) BUCKETS 8
PROPERTIES (
"enable_unique_key_merge_on_write" = "true",
"function_column.sequence_col" = "update_time"
);
-- Flink SQL配置
CREATE TABLE mysql_source (
order_id BIGINT,
status VARCHAR(20),
amount DECIMAL(10,2),
update_time TIMESTAMP(3),
PRIMARY KEY (order_id) NOT ENFORCED
) WITH (
'connector' = 'mysql-cdc',
'hostname' = 'mysql-host',
'port' = '3306',
'username' = 'user',
'password' = 'pass',
'database-name' = 'db',
'table-name' = 'orders'
);
CREATE TABLE doris_sink (
order_id BIGINT,
status VARCHAR(20),
amount DECIMAL(10,2),
update_time TIMESTAMP(3),
PRIMARY KEY (order_id) NOT ENFORCED
) WITH (
'connector' = 'doris',
'fenodes' = 'fe_host:8030',
'table.identifier' = 'db.order_cdc',
'username' = 'user',
'password' = 'pass',
'sink.properties.format' = 'json',
'sink.properties.read_json_by_line' = 'true'
);
INSERT INTO doris_sink SELECT * FROM mysql_source;
4.2 宽表实时拼接
利用部分列更新实现多源数据实时合并:
sql复制-- 创建用户宽表
CREATE TABLE user_wide (
user_id BIGINT,
basic_info JSON,
behavior_stats JSON,
purchase_stats JSON,
update_time DATETIME
) UNIQUE KEY(user_id)
DISTRIBUTED BY HASH(user_id) BUCKETS 12
PROPERTIES (
"enable_unique_key_merge_on_write" = "true",
"store_row_column" = "true" -- 开启行存优化部分更新
);
-- 不同数据源只更新自己负责的列
-- 基础信息更新
curl ... -H "partial_columns:true" -H "columns:user_id,basic_info,update_time" ...
-- 行为数据更新
curl ... -H "partial_columns:true" -H "columns:user_id,behavior_stats,update_time" ...
4.3 性能优化建议
-
写入优化:
- 批量导入:单批次建议1-10MB数据量
- 并行导入:每个BE节点2-3个并发为宜
- 避免高频小事务:合并小批量写入
-
部分列更新优化:
- 宽表(>50列)建议开启行存
- 使用SSD存储提升随机读性能
- 控制单次更新的列数(<总列数的20%)
-
删除优化:
- 大范围删除使用
TRUNCATE PARTITION替代DELETE - 定期执行
COMPACT命令触发物理清理 - 避免高频单条删除操作
- 大范围删除使用
5. 版本演进与最佳实践
从实际使用经验看,Doris的增量更新能力在不同版本有显著提升:
- 1.2.x:基础Unique Key模型,MoR实现
- 2.0.x:引入MoW、部分列更新
- 2.1.x:优化Compaction策略,减少写放大
生产环境建议:
sql复制-- 建表示例包含所有优化参数
CREATE TABLE optimal_table (
id BIGINT,
data VARCHAR(200),
metric1 BIGINT,
metric2 DECIMAL(20,6),
update_time DATETIME
) UNIQUE KEY(id)
DISTRIBUTED BY HASH(id) BUCKETS 16
PROPERTIES (
"enable_unique_key_merge_on_write" = "true",
"function_column.sequence_col" = "update_time",
"store_row_column" = "true",
"disable_auto_compaction" = "false",
"enable_single_replica_compaction" = "true",
"compaction_policy" = "size_based",
"storage_format" = "v2"
);
对于需要同时支持高频更新和分析查询的场景,建议:
- 使用SSD存储
- 预留30%的CPU资源给Compaction
- 监控
be_compaction_score指标,避免积压
