1. 为什么 ClickHouse 的"删除"不简单
传统数据库的 DELETE 是"改一行";ClickHouse 的 MergeTree 以不可变 Part 存储,没有"改一行"的能力。数据一旦落盘为 Part,修改就必须重写整个 Part。
OLTP DELETE:定位行 → 改 → 即时生效
ClickHouse DELETE:
发起 mutation → 后台任务逐 Part 重写 → 被删数据移出 → 新 Part 替换旧 Part
代价:重写涉及的 Part 越多,耗时越长
一句话总结:ClickHouse 的更新删除是"异步批处理重写",不是"即时改行"——理解这一点,才能理解 mutation 的代价与正确用法。
2. Mutation 机制深入
2.1 基本语法
-- 更新(重写 Part)
ALTER TABLE events
UPDATE status = 'archived'
WHERE ts < '2025-01-01';
-- 删除
ALTER TABLE events
DELETE WHERE user_id = 0;
2.2 执行过程
1. 提交 mutation 命令
2. 后台线程扫描各 Part,标记"需要处理的 Part"
3. 逐 Part 重写:复制未受影响行 + 写入被修改行
4. 新 Part 替换旧 Part(原子切换)
5. 全部完成后 mutation 标记完成
2.3 性能影响
| 因素 | 影响 |
|---|---|
| Part 大小 | 越大重写越慢 |
| 命中行比例 | 需读全 Part 判断 |
| 涉及 Part 数 | 决定总耗时 |
| 与合并并行 | mutation 与 merge 争资源 |
2.4 监控 mutation
-- 查看进行中的 mutation
SELECT
database, table, command,
parts_to_do, -- 待处理 Part 数
parts_done,
is_done,
latest_failed_part,
last_update_time
FROM system.mutations
WHERE NOT is_done;
2.5 清理 mutation 历史
-- mutation 完成后删除历史记录,避免 system.mutations 膨胀
KILL MUTATION WHERE database = 'default' AND table = 'events';
一句话总结:mutation 是"标记 → 逐 Part 重写 → 原子替换"的异步批处理;用 system.mutations 盯进度,用 KILL MUTATION 清历史。
3. 轻量删除(Lightweight Delete)
3.1 原理
轻量删除不再重写整个 Part,而是用**删除掩码(delete mask)**标记:
传统 DELETE:重写 Part,删除的行物理消失
轻量删除:Part 内写入一个"删除了哪些行"的掩码
→ 查询时跳过被标记行
→ 删除操作瞬时完成
→ 数据最终由后台 merge 真正物理清除
3.2 使用
-- 默认启用轻量删除(enable_lightweight_delete=1)
DELETE FROM events WHERE user_id = 0;
-- 检查是否轻量
SELECT mutation_type FROM system.mutations;
3.3 对比
| 维度 | 传统 Mutation | 轻量删除 |
|---|---|---|
| 执行时间 | 慢(重写 Part) | 快(写掩码) |
| 立即生效 | 需等重写完成 | 查询立即跳过 |
| 空间 | 重写释放 | 掩码 + 原数据(merge 后释放) |
| 物理清除 | 即时 | merge 时 |
| 适用 | 大批量、想立即释放空间 | 频繁小删除 |
一句话总结:轻量删除用"掩码标记"换"瞬时删除",适合频繁小批量清理;物理空间要等后台 merge 才真正释放。
4. TTL 生命周期管理
TTL(Time To Live)让数据按时间自动老化:过期即删除或迁移,无需人工清理。
4.1 TTL 三种作用范围
| 范围 | 效果 | 示例 |
|---|---|---|
| 列级 | 指定列到期置默认值 | TTL ts + INTERVAL 30 DAY |
| 行级 | 整行到期删除 | TTL expire_at |
| 分区级 | 分区整体删除/移动 | TTL toDate(ts) + INTERVAL 1 YEAR |
4.2 列级 + 行级组合
CREATE TABLE events (
ts DateTime,
user_id UInt64,
raw_data String TTL ts + INTERVAL 7 DAY, -- 7 天后清空明细
status UInt8
) ENGINE = MergeTree()
PARTITION BY toDate(ts)
ORDER BY (user_id, ts)
TTL ts + INTERVAL 90 DAY; -- 90 天后整行删除
4.3 分区级 TTL(冷热分层)
-- 30 天前的分区迁移到冷存储
TTL toDate(ts) + INTERVAL 30 DAY
TO VOLUME 'cold';
4.4 TTL 执行机制
后台 TTL 任务周期扫描(由 merge/part 生命周期驱动)
→ 触发过期列置默认 / 行删除 / 分区迁移
→ 合并时一并清理
→ 可手动触发 OPTIMIZE 强制 TTL
-- 手动触发 TTL 清理
OPTIMIZE TABLE events FINAL;
一句话总结:TTL 把"时间维度上的数据治理"自动化——列级弃明细、行级弃记录、分区级迁移冷存储,三档粒度覆盖完整生命周期。
5. TTL 存储策略与冷热分层
5.1 多卷(Volume)配置
<clickhouse>
<storage_configuration>
<disks>
<hot>
<type>local</type>
<path>/mnt/hot/</path>
</hot>
<cold>
<type>local</type>
<path>/mnt/cold/</path>
</cold>
</disks>
<policies>
<hot_cold>
<volumes>
<hot_vol><disk>hot</disk></hot_vol>
<cold_vol><disk>cold</disk></cold_vol>
</volumes>
</hot_cold>
</policies>
</storage_configuration>
</clickhouse>
5.2 表绑定策略
CREATE TABLE events
ENGINE = MergeTree()
PARTITION BY toDate(ts)
ORDER BY ts
SETTINGS storage_policy = 'hot_cold'
TTL toDate(ts) + INTERVAL 30 DAY TO VOLUME 'cold_vol';
5.3 分层收益
热卷(SSD):最近 30 天,高频查询
冷卷(HDD):历史数据,低访问率
→ 用低成本存储装下大量历史,热查询不受影响
一句话总结:TTL + 多卷策略把"热数据在 SSD、冷数据在 HDD"变成自动化规则——存储成本与查询热度的匹配即冷热分层。
6. mutation 与 TTL 的实践策略
6.1 大规模清理的推荐路径
优先 TTL 而非 DELETE:
能按时间清理 → 用 TTL(自动、低代价)
必须按条件清理 → 用轻量删除(快)
必须立即释放空间 → 传统 mutation + OPTIMIZE
小技巧:
分块执行(WHERE + LIMIT 分批)
避开高峰期(mutation 吃 IO/CPU)
清理后 SYSTEM OPTIMIZE 释放空间
6.2 常见陷阱
| 陷阱 | 后果 | 应对 |
|---|---|---|
| 大表全量 DELETE | 长时间重写、占资源 | 分批 + 轻量删除 |
| 频繁 mutation | Part 碎片、合并压力 | 归并批量、少做小删 |
| 忽略 mutation 监控 | 卡住不知情 | 盯 system.mutations |
| TTL 与分区键不匹配 | TTL 失效 | TTL 用分区键同时间源 |
| 热表强 merge | 写入抖动 | 调 merge 并发、错峰 |
6.3 监控数据生命周期
-- 各分区大小与新旧
SELECT
partition,
count() AS parts,
sum(rows) AS rows,
min(min_time) AS oldest,
max(max_time) AS newest,
sum(bytes_on_disk) AS bytes
FROM system.parts
WHERE table = 'events' AND active
GROUP BY partition
ORDER BY partition;
一句话总结:数据治理的正确姿势是"能 TTL 就 TTL、要即时就轻量删除、必须重写才 mutation"——三选一,配合分区监控看效果。
7. 与其他引擎/场景的配合
7.1 分布式表 + TTL
-- 分布式表同样支持 TTL(在每个分片生效)
CREATE TABLE events_dist AS events
ENGINE = Distributed(cluster, 'default', 'events');
7.2 物化视图 + TTL
-- 聚合表也设 TTL,与原始表错开保留
CREATE MATERIALIZED VIEW mv_5m
ENGINE = AggregatingMergeTree()
ORDER BY bucket
TTL toDate(bucket) + INTERVAL 180 DAY
AS SELECT ...;
7.3 冷备配合
TTL 删除前:可用分区级备份做"删除前归档"
备份旧分区 → 清空 → 需要时恢复
8. 生产实践清单
| 主题 | 核心结论 |
|---|---|
| Mutation 本质 | 异步逐 Part 重写,非即时改行 |
| 轻量删除 | 掩码标记瞬时生效,merge 才物理清除 |
| TTL 三档 | 列级弃明细、行级弃记录、分区级迁移 |
| 冷热分层 | TTL + 多卷策略自动迁移冷数据 |
| 清理策略 | 能 TTL 优先、要即时轻量删、必须重写才 mutation |
| 监控 | system.mutations / system.parts 双看板 |
数据生命周期管理,是 ClickHouse 生产运维最容易忽略却最值得投入的部分:TTL 自动化省掉人力,轻量删除保住写入性能,mutation 留给真正需要重写的场景——三者组合,让"数据增长"从负担变成可管理的资产。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。