Doris 数据更新指南

Doris 数据更新指南

适用版本:Apache Doris 2.0+(部分能力为 2.1 / 4.1+ 特性,文中已标注)
整理日期:2026-10-28
面向对象:集团技术部 Doris 使用与维护人员

目录

  1. 核心认知

  2. 三种模型的更新能力

  3. 更新路径一:导入式更新(UPSERT,首选)

  4. 更新路径二:UPDATE 语句(低频条件更新)

  5. 删除也是更新:四种删除手段

  6. 机制原理(调优前必须懂)

  7. 更新方案决策速览

  8. 最佳实践清单

  9. 常见问题(FAQ)

  10. 参考资料


1. 核心认知

Doris 的「更新」和 MySQL 完全不同:它不是原地修改数据,而是写入一条带相同主键的新记录,把旧记录「盖掉」(UPSERT 语义)。由此得出三条基本结论:

  1. 只有 Unique Key(主键)模型具备真正的行级更新能力;Aggregate 模型按聚合语义更新;Duplicate 模型只能追加,更新靠「删除 + 重导」。

  2. 更新有两条路径:导入式更新(推荐,高频大批量) 与 DML 更新(UPDATE 语句,低频条件式)。

  3. 删除本质也是更新:写入 __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 删除标记三阶段

  1. 执行删除:通过导入或 DELETE 语句删除时,不立即物理删除,而是写入 __DORIS_DELETE_SIGN__=1 的标记记录;

  2. 查询过滤:查询自动附加 WHERE __DORIS_DELETE_SIGN__ = 0,隐藏标记删除的行;

  3. 后台 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. 更新方案决策速览

flowchart TD A[业务需要更新吗?] -->|不需要, 只追加| B[Duplicate 明细模型<br>仅支持 DELETE 删后重导] A -->|需要行级更新/部分列更新| C[Unique Key 模型<br>MoW + 可选 Sequence 列] A -->|只要固定汇总指标| D[Aggregate 模型<br>按聚合语义导入更新] C --> E{更新频率与形态} E -->|高频/大批量/CDC/宽表装配| F[导入式 UPSERT<br>Stream / Routine / Broker Load, INSERT INTO<br>部分列更新 partial_columns] E -->|低频/条件式/跨表关联| G[UPDATE 语句<br>仅 Value 列, 串行执行] F --> H{同时需要删除吗?} G --> H H -->|批量主键删除 CDC| I[删除标记 __DORIS_DELETE_SIGN__] H -->|按条件删除| J[DELETE 谓词 / 分区] H -->|清空分区/整表重做| K[TRUNCATE PARTITION / TABLE] H -->|重写分区且无空窗| L[临时分区 + 原子替换]
场景 推荐做法
订单状态、用户标签高频变更 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. 最佳实践清单

  1. 高频大批量更新走导入(Stream Load / Routine Load),别用 UPDATE DML;

  2. 避免高频单行 INSERT(>100 TPS):每次 INSERT 都有事务开销,可开启 Group Commit 合并小事务;

  3. Duplicate/Aggregate 表上慎用高频 DELETE:删除谓词累积会影响后续查询性能;

  4. 整分区删除用 TRUNCATE PARTITION,比 DELETE 高效得多;

  5. UPDATE 任务串行执行,避免同主键并发(并发 UPDATE 不保证隔离);

  6. 宽表部分列更新:SSD + 行存,控制读放大 / 写放大;

  7. 存算分离架构(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 官方文档整理,语法细节以集群实际版本的官方文档为准。
qrcode_for_gh_c7fda9436d1b_344.jpg

posted @ 2026-10-29 11:24  东百牧码人  阅读(13)  评论(0)    收藏  举报