ClickHouse表必须显式声明PARTITION BY toYYYYMM(event_time)才能按月分区;仅用TTL不会自动创建物理分区,DROP PARTITION也无法使用。

ClickHouse 表必须显式声明 PARTITION BY toYYYYMM(event_time)
不写 PARTITION BY 就不会分区,哪怕你字段里有时间、哪怕你用了 TTL,也不会生成按月的物理分区。很多用户误以为只要加了 TTL 就能“自动分区”,结果发现 system.parts 里全是 all 分区,DROP PARTITION 根本没法用。
正确做法是建表时就绑定时间分区表达式:
CREATE TABLE logs (
event_time DateTime,
url String,
status UInt16
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_time) -- 关键:按年月生成分区,如 202610
ORDER BY (event_time, url)
TTL event_time + INTERVAL 90 DAY DELETE;-
toYYYYMM()生成整数型分区 ID(如202610),比字符串更省内存、排序更快 - 必须搭配
ORDER BY,且event_time建议放在排序前缀里,否则分区裁剪可能失效 - 不要用
toStartOfMonth(event_time)—— 它返回Date类型,会导致分区名变成2026-10-01这种带连字符的字符串,后续运维脚本容易出错
冷节点丢弃过期分区不能靠 TTL DELETE,得用 DROP PARTITION
TTL ... DELETE 是行级异步清理:它依赖后台 merge 任务扫描每个 part,逐行判断是否过期,再重写新 part。对 90 天前的整月数据来说,这完全是杀鸡用牛刀——既慢又占 I/O,还无法保证即时性。
真正高效的做法是:等整个月分区完全过期后,直接删分区目录。前提是你的分区键足够“干净”,比如 toYYYYMM(event_time) 能确保一个分区里所有数据都落在同一月内。
- 查出可安全删除的分区:
SELECT partition FROM system.parts WHERE table = 'logs' AND active AND toYYYYMM(max_date) (假设今天是 2026-10-02,90 天前是 2026-07-04,那 <code>202607及更早分区全可删) - 执行删除:
ALTER TABLE logs DROP PARTITION ID '202607'(注意:用ID而非值,避免引号歧义) - 这个操作是元数据级的,毫秒级完成,不触发任何数据重写
冷节点上执行 DROP PARTITION 前必须确认副本状态
如果你用的是 ReplicatedMergeTree,DROP PARTITION 是同步操作:它会先在 ZooKeeper 创建一个 mutation 记录,然后各副本依次执行。如果某个副本宕机或延迟高,整个删除会被卡住,system.mutations 里能看到 is_done = 0 的 pending 状态。
- 删之前先检查:
SELECT * FROM system.replicas WHERE table = 'logs' AND is_readonly,确保没副本处于只读态 - 查看阻塞原因:
SELECT database, table, mutation_id, command, latest_failed_part, latest_fail_time FROM system.mutations WHERE is_done = 0 - 冷节点通常网络或磁盘较弱,建议在低峰期执行,避免和大查询争抢资源
自动化脚本里别硬编码分区 ID,要用 toYYYYMM(today() - INTERVAL 90 DAY)
很多人写定时任务,直接拼字符串 "202607",结果一到跨年/跨月就漏删或误删。ClickHouse 支持在 SQL 里用函数动态计算分区 ID,更可靠。
例如,在 clickhouse-client 中执行:
ALTER TABLE logs DROP PARTITION ID toString(toYYYYMM(today() - INTERVAL 90 DAY));
-
toString()把整数转成字符串,适配DROP PARTITION ID语法 - 别用
formatDateTime():它返回带分隔符的字符串(如'2026-07'),而toYYYYMM()返回纯数字202607,跟实际分区 ID 一致 - 如果用外部脚本(如 Python),也建议调用
toYYYYMM()函数查出目标 ID 再执行,而不是靠本地时间库算——避免时区或夏令时误差
真正难的不是写对那条 DROP PARTITION,而是确保每次执行时,目标分区确实已无任何未合并的旧 part、副本全部在线、且没有正在运行的长事务锁表。这些细节在监控里往往不显眼,但一出问题就是整月数据删不掉。

















