在 Doris 中如何执行数据删除(Delete)?物理删除和逻辑标记删除在底层合并(Compaction)时分别是如何被处理的[1]?
在 Apache Doris 中,由于其底层采用了类似 LSM-Tree 的追加写(Append-only)存储架构,数据的删除(Delete)并不是直接在磁盘文件上进行就地修改,而是经历“逻辑标记删除 -> 读时合并/写时合并过滤 -> 后台 Compaction 物理删除”的过程。
以下是关于如何在 Doris 中执行删除,以及底层 Compaction 如何处理物理和逻辑删除的详细解析。
一、 在 Doris 中如何执行数据删除?
Doris 提供了三种主要的数据删除方式,适用于不同的业务场景:
1. SQL DELETE 语句(基于删除条件)
这是最常用的删除方式,通过指定 WHERE 条件来删除数据。
- 语法:sql
DELETE FROM table_name [PARTITION partition_name] WHERE column_name op value [AND column_name op value ...]; - 限制:Doris 为了防止误操作,通常要求
WHERE子句中必须包含 Key 列(对于 Unique/Aggregate 模型)或部分列条件,且不支持全表无条件删除(清空表请使用TRUNCATE)。 - 底层原理:FE(Frontend)会将该删除语句转化为一个 Delete Predicate(删除断言/条件),并作为一个特殊版本的数据写入到存储引擎中,记录在 Tablet 的元数据中。
2. 导入时通过 Delete Sign 批量删除(针对 Unique Key 模型)
在 Unique Key 模型中,Doris 支持通过导入(Stream Load, Flink CDC, Spark Load 等)直接写入“删除标记”。
- 实现方式:在创建 Unique Key 表时,可以开启
__DORIS_DELETE_SIGN__隐式列,或者显式指定一个序列列。 - 数据格式:导入的数据中包含一列,值为
1表示删除该 Key,值为0表示正常写入。CSV 数据示例:bashcurl --location-trusted -u root: -H "columns: k1, k2, v1, __DORIS_DELETE_SIGN__" \ -T data.csv http://fe_host:http_port/api/test_db/test_table/_stream_loadkey1, value1, 1(表示删除 key1)
3. 物理分区删除(最推荐的高效删除)
如果要删除的数据具有时间维度,且表进行了分区,直接 DROP 或 TRUNCATE 分区是最高效的。
- 语法:sql
ALTER TABLE tbl DROP PARTITION p1; -- 直接删除分区(放入回收站) TRUNCATE TABLE tbl PARTITION p1; -- 清空分区数据 - 优点:这是直接的物理删除,不经过 Compaction,瞬间释放磁盘空间。
二、 逻辑删除与物理删除在 Compaction 时的处理机制
在 Doris 中,通过 SQL DELETE 或 Delete Sign 写入的删除操作,在初期都是逻辑删除。这些删除标记在后台通过 Compaction(数据合并) 逐步转化为物理删除(真正从磁盘上抹去并释放空间)。
Doris 的 Unique Key 模型有两种实现机制,它们的处理方式不同:Merge-on-Read (MoR, 读时合并) 和 Merge-on-Write (MoW, 写时合并)。
1. Merge-on-Read (MoR) 与 Duplicate/Aggregate 模型
在这类模型中,删除通过 Delete Predicate(删除断言) 或 Delete Sign(删除标记) 实现。
A. Cumulative Compaction (CC - 增量合并) 阶段
- 处理逻辑:CC 负责将新写入的多个小的 Rowset(数据批次)合并成较大的 Rowset。
- 逻辑/物理处理:
- 在 CC 阶段,不会真正物理删除数据。
- 如果合并的 Rowset 中包含 Delete Predicate,这些 Predicate 会随着 Rowset 的合并被整理在一起,但依然保留。
- 因为 CC 只合并最近写入的数据,而需要被删除的历史数据可能存在于更老的 Base Rowset 中,所以此时不能丢弃删除条件,否则历史数据就“复活”了。
B. Base Compaction (BC - 基线合并) 阶段
- 处理逻辑:BC 负责将 Base Rowset(最老的、最大的底座数据)与最新的 Cumulative Rowset 合并。这是物理删除发生的关键时刻。
- 物理删除处理:
- 针对 Delete Predicate(删除条件):当进行 BC 时,Doris 会读取旧的 Base 数据和删除条件。在内存中进行 merge 排序时,所有匹配删除条件的行都会被直接过滤掉(丢弃),不写入新生成的 Base Rowset 文件中。
- 针对 Delete Sign(删除标记):如果在合并过程中,发现同一个 Key 的最新版本是一个
Delete Sign = 1的行,且确认该 Key 之前的所有旧版本数据都包含在本次合并的 Rowset 范围内,那么这个 Key 的所有历史版本以及这个Delete Sign本身,都不会被写入新产生的 Rowset 中。
- 元数据清理:一旦某个 Delete Predicate 应用到了 Base Rowset,并且其版本(Version)已经低于当前的 Base Version,该删除条件就会从 Tablet 的元数据中彻底移除。
2. Merge-on-Write (MoW) 模型 (Unique Key 默认/推荐)
Doris 在 1.2 版本后引入了 MoW 机制,它在写入时就会通过 Delete Bitmap(删除位图) 标记哪些行已被删除。
- 逻辑删除阶段:
- 当执行删除(或更新)时,Doris 不会修改原数据文件,而是在 Delete Bitmap 中将原数据所在的行号(RowID)标记为“已删除”。
- 在查询时,直接读取最新数据并利用 Delete Bitmap 过滤掉被标记的行,速度极快。
- Compaction 时的物理删除:
- 无论是 CC 还是 BC,Compaction 线程在合并 Rowset 时,都会读取对应的 Delete Bitmap。
- 在将数据从旧的 Segment 复制到新 Segment 的过程中,Compaction 引擎会直接跳过那些在 Delete Bitmap 中被标记为“已删除”的行。
- 合并完成后,会生成一个完全不包含已删除行的新 Rowset,同时旧的 Rowset 和对应的 Delete Bitmap 会被标记为过期。
- 磁盘释放:当旧的 Rowset 文件的引用计数归零(通常在 10~30 分钟后,由垃圾回收线程 GC 执行),旧文件被彻底物理删除,磁盘空间得到释放。
三、 总结:如何判断空间是否释放?
- 逻辑删除:
DELETE执行成功后,数据在查询时立即可见已被删除,但磁盘空间不会立刻释放。 - 物理删除:
- 普通 Delete 语句:需等待 Base Compaction 执行。由于 Base Compaction 消耗 CPU/IO 较大,触发频率较低,空间释放可能需要数小时或数天。
- MoW 模型的 Delete:在 Cumulative Compaction 时就能逐步物理删除,空间释放速度快于 MoR。
- 分区 Drop/Truncate:立即物理删除,空间迅速释放。
可以通过 SHOW TABLET STATUS FROM table_name; 查看 Tablet 的版本数量(Version Count)和数据大小。当 Version Count 减少,且没有历史过期的 Rowset 引用时,磁盘空间即完成物理释放。