Doris 数据更新指南
Doris 数据更新指南
适用版本:Apache Doris 2.0+(部分能力为 2.1 / 4.1+ 特性,文中已标注)
整理日期:2026-10-28
面向对象:集团技术部 Doris 使用与维护人员
目录
1. 核心认知
Doris 的「更新」和 MySQL 完全不同:它不是原地修改数据,而是写入一条带相同主键的新记录,把旧记录「盖掉」(UPSERT 语义)。由此得出三条基本结论:
-
只有 Unique Key(主键)模型具备真正的行级更新能力;Aggregate 模型按聚合语义更新;Duplicate 模型只能追加,更新靠「删除 + 重导」。
-
更新有两条路径:导入式更新(推荐,高频大批量) 与 DML 更新(UPDATE 语句,低频条件式)。
-
删除本质也是更新:写入
__DORIS_DELETE_SIGN__=1的删除标记,后台 Compaction 再物理清理。
2. 三种模型的更新能力
| 表模型 | 数据组织 | 支持的操作 | 典型场景 |
|---|---|---|---|
| Unique Key(主键) | 按主键写时去重 | UPSERT、部分列更新、UPDATE、删除标记、DELETE |
订单状态变更、用户标签、CDC 同步 |
| Aggregate Key(聚合) | 同 Key 按聚合函数合并 | 仅按聚合语义导入更新;DELETE 只能按 Key 列 条件 |
实时报表、点击计数 |
| Duplicate Key(明细) | 只追加,不去重不聚合 | 仅支持 DELETE |
日志、行为追踪 |
结论:需要行级更新或部分列更新,就选 Unique Key 模型(建表时定死,模型不可修改,详见《Doris 建表指南》)。
3. 更新路径一:导入式更新(UPSERT,首选)
所有导入方式(Stream Load / Broker Load / Routine Load / INSERT INTO)在 Unique Key 表上默认就是 UPSERT 语义:
-
主键已存在 → 新行覆盖旧行;
-
主键不存在 → 插入新行。
适用:高频、大批量更新、CDC 同步、多源宽表装配。推荐用 Stream Load / Routine Load 持续写入,而不是逐条 UPDATE。
3.1 全行覆盖(默认)
# 示例:Stream Load 全行 UPSERT(JSON 格式)
curl --location-trusted -u root: -H "format:json" \
-H "columns:order_id,status,update_time" \
-T update.json \
http://127.0.0.1:8030/api/db/orders/_stream_load
# 同主键数据到达即覆盖旧行
3.2 部分列更新(2.0+,Unique Key MoW)
只传「主键 + 要改的列」,未提供的列保持原值。开启方式:
| 导入方式 | 开启配置 |
|---|---|
INSERT INTO |
会话变量 SET enable_unique_key_partial_update = true |
| Stream Load 等导入 | 请求参数 partial_columns: true |
CREATE TABLE user_profiles (
user_id BIGINT, name STRING, age INT, last_login DATETIME
) UNIQUE KEY(user_id)
DISTRIBUTED BY HASH(user_id)
PROPERTIES ("enable_unique_key_merge_on_write" = "true");
-- Stream Load 只写 age 和 last_login,name 保持不变
-- {"user_id": 1, "age": 31, "last_login": "2026-10-28 18:00:00"}
注意:部分列更新不是原地改,而是「读旧行补齐缺失列再整行重写」,存在读放大与写放大 ——100 列宽表只改 10 列,约产生 9 倍读、10 倍写。建议:用 SSD(NVMe);超宽表可开启行存减少 IO。
4. 更新路径二:UPDATE 语句(低频条件更新)
支持标准 SQL UPDATE,但仅限 Unique Key 模型、且只能更新 Value 列(Key 列更新需走 FlinkCDC 等重写方式)。
4.1 语法
[cte]
UPDATE target_table [table_alias]
SET assignment_list
[FROM additional_tables]
[WHERE condition]
[ORDER BY column [ASC | DESC] ...]
[LIMIT [offset,] count]
4.2 示例
-- 1. 简单条件更新
UPDATE user_profiles SET age = age + 1 WHERE user_id = 1;
-- 2. 跨表关联更新(FROM 子句)
UPDATE sales_records t1
SET t1.user_name = t2.name
FROM user_profiles t2
WHERE t1.user_id = t2.user_id;
-- 3. CTE + 关联更新
WITH discount_orders AS (
SELECT * FROM orders WHERE o_totalprice > 100
)
UPDATE lineitem SET l_discount = l_discount * 0.9
FROM discount_orders
WHERE lineitem.o_orderkey = discount_orders.o_orderkey;
-- 4. ORDER BY + LIMIT 定点更新(按 v1 降序更新前 3 行)
UPDATE test SET v1 = 0 WHERE k1 > 0 ORDER BY v1 DESC LIMIT 3;
-- 5. ORDER BY + LIMIT + offset(跳过前 10 行,更新接下来的 5 行)
UPDATE test SET v1 = 100 ORDER BY k1 ASC LIMIT 10, 5;
4.3 执行机制与限制
-
执行过程:先扫描命中条件的数据,把更新后的行写回 —— 适合低频、批量、条件式刷新,不适合高频大批量;
-
未指定
WHERE和LIMIT会更新全表,执行前务必确认范围; -
同一主键的并发 UPDATE 不保证隔离,涉及相同主键的更新任务要串行执行;
-
用
INSERT INTO ... SELECT ...(UPSERT 语义)也可以达到批量更新效果。
5. 删除也是更新:四种删除手段
| 手段 | 语法 | 适用 | 模型 |
|---|---|---|---|
| 条件删除 | DELETE FROM t WHERE k1 = 3;(支持 = > < >= <= != IN NOT IN) |
常规按条件删除 | 全部模型 |
| 分区删除 | DELETE FROM t PARTITION (p1) WHERE ...; |
分区内过滤 | 全部模型 |
| TRUNCATE 分区 / 整表 | TRUNCATE TABLE t PARTITION (p1, p2); / TRUNCATE TABLE t; |
清空过期分区 / 整表重做,只改元数据,最快 | 全部模型 |
| 关联删除 | DELETE FROM t1 USING t2 INNER JOIN t3 ... WHERE t1.id = t2.id; |
多表关联精确定位 | 仅 Unique Key |
5.1 DELETE 使用限制
-
聚合表只能在 Key 列 上指定删除条件;若选定的 Key 列不存在于某个 Rollup 中,则无法执行删除;
-
分区表需指定分区或能由条件推断;条件不含分区列、或分区列用
not in时无法推断,可显式指定PARTITION或SET delete_without_partition = true; -
IN谓词元素上限默认 1024(可调全局参数max_allowed_in_element_num_of_delete); -
删除超时可通过
SET insert_timeout = xxx(秒)调整; -
SHOW DELETE [FROM db_name]查看删除历史。
5.2 批量主键删除(CDC 场景)
CDC 程序把上游 DELETE 的 binlog 转为删除标记写入(__DORIS_DELETE_SIGN__=1),一次导入可批量删大量主键,比逐条 DELETE 高效。
5.3 原子覆盖写
需要重写某分区且不允许中间空窗时 —— 先建临时分区、导入新数据,再通过表原子替换(SWAP PARTITION)替换原分区,避免「先删后导」的不可见窗口。
5.4 不同模型的 DELETE 性能特征
| 表模型 | 执行速度 | 对查询的影响 | 建议 |
|---|---|---|---|
| 明细表(Duplicate) | 快 | 短时间内大量删除会影响查询性能 | 控制删除频率 |
| 聚合表(Aggregate) | 快 | 短时间内大量删除会影响查询性能 | 控制删除频率 |
| 主键表(Unique) | 大范围删除较慢(转换为 INSERT INTO) | 短时间内大量删除对查询影响较小 | 适合频繁删除场景 |
6. 机制原理(调优前必须懂)
6.1 MoW 与 MoR
Unique Key 表有两种合并策略,2.1 起默认 Merge-on-Write(MoW):
| 维度 | MoW(写时合并) | MoR(读时合并,旧版) |
|---|---|---|
| 写入 | 写时去重合并,主键只留最新一条 | 写入保留多版本 |
| 查询 | 接近无更新的明细表 | 查询时实时合并,慢约 3–10 倍 |
| 写入性能 | 有合并开销,小批量慢 10–20%,大批量慢 30–50% | 接近明细表 |
| 资源消耗 | 写入 / Compaction 耗 CPU 内存 | 查询耗 CPU 内存 |
| 适用场景 | 读多写少(推荐) | 写多读少(不推荐) |
6.2 删除标记三阶段
-
执行删除:通过导入或
DELETE语句删除时,不立即物理删除,而是写入__DORIS_DELETE_SIGN__=1的标记记录; -
查询过滤:查询自动附加
WHERE __DORIS_DELETE_SIGN__ = 0,隐藏标记删除的行; -
后台 Compaction:检测到「正常记录 + 删除标记」成对出现时,合并时物理清除两行,释放存储空间。
代价:删除后存储空间不会立即释放;频繁删除会增加版本数量,影响查询性能。
6.3 Sequence 列解决乱序
分布式环境下数据可能乱序到达(如订单状态 "Paid" 与 "Shipped" 先后颠倒)。建表指定 Sequence 列(时间戳 / 版本号),同主键写入时永远保留 Sequence 值最大的行:
CREATE TABLE order_status (
order_id BIGINT,
status_name STRING,
update_time DATETIME
) UNIQUE KEY(order_id)
DISTRIBUTED BY HASH(order_id)
PROPERTIES ("function_column.sequence_col" = "update_time");
-- 先到 "Shipped"(12:00),后到 "Paid"(11:00) → 保留 update_time 更大的 "Shipped"
多流并发更新宽表不同列时,可使用 Sequence Mapping 对各列分别控制版本顺序。注意:Stream Load / Broker Load 等导入中须显式指定 Sequence 列,否则报错(Sequence 列默认值为 CURRENT_TIMESTAMP 的除外)。
7. 更新方案决策速览
| 场景 | 推荐做法 |
|---|---|
| 订单状态、用户标签高频变更 | Unique Key + MoW + Sequence 列,导入式 UPSERT |
| MySQL / 外部库实时同步(CDC) | Unique Key 表 + Streaming Job / Flink CDC,删除事件映射 __DORIS_DELETE_SIGN__ |
| 多源宽表装配 | Unique Key 表 + 部分列更新,各流只写自己负责的列 |
| 低频条件刷新(如按状态批量改值) | UPDATE 语句或 INSERT INTO ... SELECT ... |
| 过期分区清理 | TRUNCATE PARTITION(比 DELETE 高效) |
| 分区数据整体重刷 | 临时分区 + 原子替换 |
8. 最佳实践清单
-
高频大批量更新走导入(Stream Load / Routine Load),别用 UPDATE DML;
-
避免高频单行 INSERT(>100 TPS):每次 INSERT 都有事务开销,可开启 Group Commit 合并小事务;
-
Duplicate/Aggregate 表上慎用高频 DELETE:删除谓词累积会影响后续查询性能;
-
整分区删除用 TRUNCATE PARTITION,比 DELETE 高效得多;
-
UPDATE 任务串行执行,避免同主键并发(并发 UPDATE 不保证隔离);
-
宽表部分列更新:SSD + 行存,控制读放大 / 写放大;
-
存算分离架构(3.0+):Unique 表 MoW 依赖分布式表锁,单表导入频率建议 ≤60 次 / 秒;分区按天 / 小时切、桶数 8–64 别贪多,减少锁竞争;优先使用 3.1+ 版本(分布式表锁已优化)。
9. 常见问题(FAQ)
Q1:UPDATE 能更新 Key 列吗?
不能。UPDATE 仅支持 Unique Key 模型且只能更新 Value 列;Key 列更新需通过 FlinkCDC 等重写方式。
Q2:DELETE 报错要求指定分区怎么办?
非 Unique 分区表条件不含分区列或使用 not in 时无法推断分区。显式指定 PARTITION,或 SET delete_without_partition = true 作用于所有分区。
Q3:聚合表上能对非 Key 列执行删除吗?
不能。聚合表只能在 Key 列上指定删除条件。
Q4:USING 关联删除支持哪些模型?
仅支持 Unique Key 模型表作为删除目标。
Q5:同主键的并发 UPDATE 会怎样?
不保证隔离。并发 UPDATE 涉及相同主键时结果不可预期,应串行执行。
Q6:删除后存储空间为何没有立即释放?
删除是逻辑标记,需等后台 Compaction 物理清理;频繁删除会积累版本,影响查询性能。
Q7:乱序数据到达如何保证最终一致?
建表指定 Sequence 列(时间戳 / 版本号),同主键永远保留 Sequence 值最大的行。
Q8:高频小批量更新性能差怎么办?
客户端攒批(数百 MB~数 GB)再导,或开启 Group Commit 服务端攒批,减少事务数、Compaction 与写放大。
10. 参考资料
-
Apache Doris 官方文档・数据更新概述:https://doris.apache.org/docs/dev/data-operate/update/update-overview/
-
Apache Doris 官方文档・UPDATE 语句:https://doris.apache.org/zh-CN/docs/dev/sql-manual/sql-statements/data-modification/DML/UPDATE/
-
Apache Doris 官方文档・DELETE 操作:https://doris.apache.org/zh-CN/docs/dev/data-operate/delete/delete-manual/
-
Apache Doris 官方文档・删除操作概述:https://doris.apache.org/zh-CN/docs/4.x/data-operate/delete/delete-overview/
-
Apache Doris 官方文档・列更新(部分列更新):https://doris.apache.org/zh-CN/docs/3.x/data-operate/update/partial-column-update/
-
Apache Doris 官方文档・导入最佳实践与性能调优:https://doris.apache.org/zh-CN/docs/4.x/data-operate/import/load-best-practices/
本指南基于 Apache Doris 官方文档整理,语法细节以集群实际版本的官方文档为准。


浙公网安备 33010602011771号