📝 摘要 :ClickHouse 线上出问题,答案几乎都在
system库里------但官方文档按表罗列字段、不按问题组织,真出事了根本不知道该查哪张。本文按症状 把常用运维 SQL 串成一份速查:现在在跑什么(processes/merges/mutations)、内存去哪了(metrics/query_log)、磁盘被谁吃了(parts)、合并为什么不动(parts+OPTIMIZE)、数据删了空间为什么没释放。附一条几乎人人中招的实测结论:system.*_log自己可能就占了一半以上磁盘。基于 ClickHouse 25.4。
ClickHouse 用久了都会遇到这么一天:磁盘告警了,可你把业务表体积加起来,怎么算都对不上;
报表跑不出来,也不知道是卡在查询、卡在合并,还是卡在某个删了一半的
ALTER;内存报警一直响,
MemoryTracking二十多个 G,你却说不清是谁吃的。这些问题的答案全都在
system库里 。麻烦在于:官方文档是按表 写的------一张表一页、把字段列全,可你出事的时候脑子里是症状 ,不是表名。中间那一层「我这个现象该查哪张表」,得自己攒。这篇就是我自己攒的那一层,按症状组织,出问题照着抄。
📚 「命令速查」系列 :PostgreSQL 常用命令速查(MySQL 用户平滑上手) · uv 常用命令速查(Python 工具链五合一) · Linux 网络排查命令速查(ss / tcpdump / mtr / dig) · Redis 排查命令速查 · Arthas 排查命令速查 · 本篇:ClickHouse 运维 SQL。
⚠️ 版本说明 :本文基于 ClickHouse 25.4 。system表的字段在版本间有增减 (比如system.parts这些年就加过visible、removal_state等列),跑不通时先DESCRIBE system.xxx看一眼当前版本有哪些列,别照抄字段名。
一、先记这张表:症状 → 查哪儿
出问题时不用回忆表名,先看这一列「你观察到的现象」:
| 你观察到的现象 | 查这张表 | 跳到 |
|---|---|---|
| 服务卡住 / 报表跑不出来 | system.processes |
[§三](#你观察到的现象 查这张表 跳到 服务卡住 / 报表跑不出来 system.processes §三 ALTER DELETE / UPDATE 半天没生效 system.mutations §三 数据重复、ReplacingMergeTree 没去重 system.parts + system.merges §六 内存报警、MemoryTracking 居高不下 system.metrics / system.query_log §四 磁盘告警,但业务表体积对不上 system.parts §五 删了数据磁盘没变小 system.mutations + system.parts(多半是行为不是故障) §八 查询走没走索引 EXPLAIN indexes = 1 §七 加的跳数索引到底有没有生效 system.data_skipping_indices §七) |
ALTER DELETE / UPDATE 半天没生效 |
system.mutations |
[§三](#你观察到的现象 查这张表 跳到 服务卡住 / 报表跑不出来 system.processes §三 ALTER DELETE / UPDATE 半天没生效 system.mutations §三 数据重复、ReplacingMergeTree 没去重 system.parts + system.merges §六 内存报警、MemoryTracking 居高不下 system.metrics / system.query_log §四 磁盘告警,但业务表体积对不上 system.parts §五 删了数据磁盘没变小 system.mutations + system.parts(多半是行为不是故障) §八 查询走没走索引 EXPLAIN indexes = 1 §七 加的跳数索引到底有没有生效 system.data_skipping_indices §七) |
数据重复、ReplacingMergeTree 没去重 |
system.parts + system.merges |
[§六](#你观察到的现象 查这张表 跳到 服务卡住 / 报表跑不出来 system.processes §三 ALTER DELETE / UPDATE 半天没生效 system.mutations §三 数据重复、ReplacingMergeTree 没去重 system.parts + system.merges §六 内存报警、MemoryTracking 居高不下 system.metrics / system.query_log §四 磁盘告警,但业务表体积对不上 system.parts §五 删了数据磁盘没变小 system.mutations + system.parts(多半是行为不是故障) §八 查询走没走索引 EXPLAIN indexes = 1 §七 加的跳数索引到底有没有生效 system.data_skipping_indices §七) |
内存报警、MemoryTracking 居高不下 |
system.metrics / system.query_log |
[§四](#你观察到的现象 查这张表 跳到 服务卡住 / 报表跑不出来 system.processes §三 ALTER DELETE / UPDATE 半天没生效 system.mutations §三 数据重复、ReplacingMergeTree 没去重 system.parts + system.merges §六 内存报警、MemoryTracking 居高不下 system.metrics / system.query_log §四 磁盘告警,但业务表体积对不上 system.parts §五 删了数据磁盘没变小 system.mutations + system.parts(多半是行为不是故障) §八 查询走没走索引 EXPLAIN indexes = 1 §七 加的跳数索引到底有没有生效 system.data_skipping_indices §七) |
| 磁盘告警,但业务表体积对不上 | system.parts |
[§五](#你观察到的现象 查这张表 跳到 服务卡住 / 报表跑不出来 system.processes §三 ALTER DELETE / UPDATE 半天没生效 system.mutations §三 数据重复、ReplacingMergeTree 没去重 system.parts + system.merges §六 内存报警、MemoryTracking 居高不下 system.metrics / system.query_log §四 磁盘告警,但业务表体积对不上 system.parts §五 删了数据磁盘没变小 system.mutations + system.parts(多半是行为不是故障) §八 查询走没走索引 EXPLAIN indexes = 1 §七 加的跳数索引到底有没有生效 system.data_skipping_indices §七) |
| 删了数据磁盘没变小 | system.mutations + system.parts(多半是行为不是故障) |
[§八](#你观察到的现象 查这张表 跳到 服务卡住 / 报表跑不出来 system.processes §三 ALTER DELETE / UPDATE 半天没生效 system.mutations §三 数据重复、ReplacingMergeTree 没去重 system.parts + system.merges §六 内存报警、MemoryTracking 居高不下 system.metrics / system.query_log §四 磁盘告警,但业务表体积对不上 system.parts §五 删了数据磁盘没变小 system.mutations + system.parts(多半是行为不是故障) §八 查询走没走索引 EXPLAIN indexes = 1 §七 加的跳数索引到底有没有生效 system.data_skipping_indices §七) |
| 查询走没走索引 | EXPLAIN indexes = 1 |
[§七](#你观察到的现象 查这张表 跳到 服务卡住 / 报表跑不出来 system.processes §三 ALTER DELETE / UPDATE 半天没生效 system.mutations §三 数据重复、ReplacingMergeTree 没去重 system.parts + system.merges §六 内存报警、MemoryTracking 居高不下 system.metrics / system.query_log §四 磁盘告警,但业务表体积对不上 system.parts §五 删了数据磁盘没变小 system.mutations + system.parts(多半是行为不是故障) §八 查询走没走索引 EXPLAIN indexes = 1 §七 加的跳数索引到底有没有生效 system.data_skipping_indices §七) |
| 加的跳数索引到底有没有生效 | system.data_skipping_indices |
[§七](#你观察到的现象 查这张表 跳到 服务卡住 / 报表跑不出来 system.processes §三 ALTER DELETE / UPDATE 半天没生效 system.mutations §三 数据重复、ReplacingMergeTree 没去重 system.parts + system.merges §六 内存报警、MemoryTracking 居高不下 system.metrics / system.query_log §四 磁盘告警,但业务表体积对不上 system.parts §五 删了数据磁盘没变小 system.mutations + system.parts(多半是行为不是故障) §八 查询走没走索引 EXPLAIN indexes = 1 §七 加的跳数索引到底有没有生效 system.data_skipping_indices §七) |
💡 一个通用前提 :
system库里带_log后缀的都是历史表 (query_log、part_log、text_log...),是写盘的真实表 ;不带_log的多是实时视图 (processes、merges、metrics...),只反映此刻 。查历史用前者,查现状用后者------这条分不清,会出现「明明刚才还在跑,怎么查不到」。
二、先连上去
sh
# 容器里连(本机部署直接跑 clickhouse-client)
docker exec -it clickhouse-server clickhouse-client
# -m 允许多行 SQL(不加的话回车就执行,写长 SQL 很难受)
clickhouse-client -m
# 想看执行细节:打开 trace 级日志,排查「慢在哪一步」很有用
clickhouse-client --send_logs_level=trace <<< "SELECT count() FROM ods.ods_tb_message"
# 直接导出 CSV(带表头)
clickhouse-client --query "SELECT count() FROM ods.ods_tb_message" --format CSVWithNames > /tmp/rs.csv
不想进容器,走 HTTP 接口(8123)也行,排查时最省事:
sh
curl 'http://localhost:8123/' --data-binary 'SELECT 1'
curl 'http://localhost:8123/?user=<user>&password=<password>' --data-binary "SELECT 1"
⚠️ 坑 :常用格式还有
TSV、JSONEachRow、Pretty。Pretty在终端看着舒服,但别用它导文件,里面全是画表格的字符。
三、现在到底在跑什么
3.1 正在执行的查询
sql
SELECT
query_id, user,
elapsed, -- 已经跑了多少秒
is_cancelled,
memory_usage, -- 此刻占用内存(字节)
peak_memory_usage, -- 到目前为止的峰值
read_rows,
query
FROM system.processes
ORDER BY elapsed DESC; -- 抓「谁在吃内存」就改成 ORDER BY memory_usage DESC
3.2 杀掉一个查询
sql
KILL QUERY WHERE query_id = 'a1b2c3d4-....';
⚠️ 坑 :
KILL QUERY是异步 的------它只是打了个「取消」标记,语句立刻返回,但查询不一定马上停 。想确认是不是真停了,回头再查一次system.processes;也可以用KILL QUERY ... SYNC等它真正结束。📌
SHOW PROCESSLIST是system.processes的简写,应急时敲着快,但不能加 WHERE 和排序,排查还是查表。
3.3 数据变更任务(ALTER DELETE / UPDATE)
这是最容易被忽略的一类------ALTER TABLE ... DELETE 不是同步的,它是一个后台 mutation:
sql
SELECT
database, table, mutation_id, command,
create_time,
is_done,
parts_to_do -- 还剩多少个 part 没处理完
FROM system.mutations
WHERE NOT is_done;
⚠️ 坑(很常见) :删了半天数据没变化,八成不是没执行,而是 mutation 还在跑 ,或者卡住了 。看
parts_to_do是不是在往下掉;如果一直不动,看latest_fail_reason字段------失败原因写在那里。mutation 卡住可以撤 :
KILL MUTATION WHERE mutation_id = '...'。
3.4 后台合并任务
sql
SELECT
database, table,
elapsed,
progress, -- 0~1 的进度
result_part_name,
source_part_names
FROM system.merges;
四、内存到底被谁吃了
4.1 先看总量
sql
SELECT metric, formatReadableSize(value) AS size
FROM system.metrics WHERE metric = 'MemoryTracking' -- CK 自己记的账
UNION ALL
SELECT metric, formatReadableSize(value)
FROM system.asynchronous_metrics WHERE metric = 'MemoryResident'; -- 进程实际占的物理内存
两个数差很多(比如前者 20G、后者 2.4G)时,先别急着找大查询,见文末延伸阅读第二篇。
4.2 再看历史上谁吃得最多
system.processes 只有此刻 在跑的,查历史必须用 query_log:
sql
SELECT
query_id, query,
formatReadableSize(memory_usage) AS peak_mem, -- query_log 里它就是峰值
query_duration_ms
FROM system.query_log
WHERE event_date = today()
AND type = 'QueryFinish'
AND memory_usage > 1e9 -- 只看超过 1GB 的
ORDER BY memory_usage DESC
LIMIT 10;
⚠️ 两个坑:
query_log一行查询会记两条 ------QueryStart和QueryFinish,不加type过滤,条数和内存都会看重。异常结束的是ExceptionBeforeStart/ExceptionWhileProcessing,查失败原因时反而要专门捞这两类。query_log只有memory_usage,记的就是这条查询的峰值 。peak_memory_usage是system.processes才有的列,照抄过来会报Unknown expression identifier。
五、磁盘被谁吃了(最容易翻车的一节)
5.1 按表排序,看谁最大
sql
SELECT
database, table,
sum(data_compressed_bytes) AS cc,
formatReadableSize(sum(data_compressed_bytes)) AS total_compressed
FROM system.parts
WHERE active = 1
GROUP BY database, table
ORDER BY cc DESC
LIMIT 20;
⚠️
active = 1这个条件不能省。 合并之后旧 part 不会立刻消失,会以active = 0的状态再留 8 分钟以上(见 §八)。不加过滤,算出来的体积会明显偏大,然后你就会去查一个根本不存在的「磁盘暴涨」。
5.2 ⭐ 实测:一半以上的磁盘,是 ClickHouse 自己的日志表吃掉的
有一次磁盘占到 280 GiB,而业务表按预估只有 100 G 出头,怎么算都对不上。按上面那条 SQL 排下来,第一名让人意外:
| database | table | total_compressed |
|---|---|---|
| system | text_log | 105.06 GiB |
| ods | (最大的业务表) | 33.80 GiB |
| system | trace_log | 23.44 GiB |
| system | part_log | 20.66 GiB |
| system | query_log | 17.96 GiB |
| ods | (第二大业务表) | 13.14 GiB |
| ... | ... | ... |
| system | metric_log | 3.22 GiB |
system.text_log 一张表 105 GiB,是最大业务表的 3 倍多 ;再算上 trace_log、part_log、query_log、query_views_log、processors_profile_log、metric_log,系统日志表合计占掉了一半以上的磁盘。
原因不复杂:这些表默认开着、默认没有 TTL (25.4 默认配置里,表里这五张都不过期),写入量跟着集群活跃度线性涨,而没人会想到去查它们 ------大家排查磁盘时,眼睛都盯在 ods、dwd 上。
怎么处理 :这些表可以安全清空 (它们的表注释里都写着 「It is safe to truncate or drop this table at any time」 ,SELECT comment FROM system.tables WHERE database = 'system' 能看到),清完不影响任何业务数据,只是丢掉历史排查线索:
sql
TRUNCATE TABLE system.text_log;
TRUNCATE TABLE system.trace_log;
💡 治本是给它们配 TTL 或直接关掉 (在
config.xml里各*_log段配置留存策略 / 注释掉对应段落)。text_log尤其值得关注------它记的是服务端日志正文 ,默认配置是<level>trace</level>,连 trace 级日志都入库,所以量最大、平时最没用。
5.3 看某张表的分区分布
sql
SELECT
partition,
count() AS parts,
sum(rows) AS rows,
formatReadableSize(sum(bytes_on_disk)) AS size,
min(min_time), max(max_time)
FROM system.parts
WHERE database = 'ods' AND table = 'ods_tb_message' AND active
GROUP BY partition
ORDER BY partition;
六、合并没动,数据一直重复
ReplacingMergeTree 只在合并时去重 。所以「数据重复」这个现象,本质上问的是「为什么还没合并」。
先看每个分区堆了多少个 part:
sql
SELECT partition, count() AS parts_count
FROM system.parts
WHERE table = 'dim_user_profile' AND active
GROUP BY partition
ORDER BY parts_count DESC;
后台合并什么时候发生是不保证的:part 少、写入停了,几个 part 可能一直就这么放着。想立刻去重就手动强合:
sql
OPTIMIZE TABLE dim_user_profile FINAL;
⚠️ 三个坑,按踩中概率排:
OPTIMIZE ... FINAL是重操作 ------不带PARTITION时会把每个分区 都重写一遍,大表上跑可能几十分钟并吃满 IO。只坏了一个分区就用OPTIMIZE TABLE t PARTITION '202601' FINAL;别在业务高峰期敲,更别写进定时任务无脑跑。FINAL关键字在两个地方含义不同 :OPTIMIZE TABLE t FINAL是真的去合并 ;SELECT ... FROM t FINAL只是查询时临时合并给你看,不落盘、而且很慢。查数临时去重用后者,治本用前者。- 「怎么合了还是重复」,查两处 :① 去重的依据是排序键(
ORDER BY),不是主键、也不是你以为的业务唯一键,ORDER BY里没带上的字段不参与判断;② 合并只在分区内发生 ,同一个 key 落在两个分区(比如分区键用了会变的时间字段),OPTIMIZE ... FINAL永远合不掉。更迷惑的是SELECT ... FINAL默认会跨分区去重,查出来是干净的,底下其实还是两行。
七、查询为什么慢:EXPLAIN 七件套
sql
EXPLAIN SELECT ... -- 默认:执行计划
EXPLAIN PLAN SELECT ... -- 同上,写全了更清楚
EXPLAIN PIPELINE SELECT ... -- 执行管道:看并行度、看各阶段
EXPLAIN indexes = 1 SELECT ... -- ⭐ 最常用:索引有没有生效、裁掉了多少 part
EXPLAIN ESTIMATE SELECT ... -- 预估要扫多少行 / 多少 part
EXPLAIN SYNTAX SELECT ... -- 优化器改写后的 SQL
EXPLAIN AST SELECT ... -- 语法树,基本只在排 SQL 解析问题时用
日常只有两条真正高频 :EXPLAIN indexes = 1 看索引裁剪,EXPLAIN ESTIMATE 看扫描量。
查跳数索引建了没、生效没:
sql
SELECT name, type, expr, granularity
FROM system.data_skipping_indices
WHERE database = 'ods' AND table = 'ods_tb_message';
⚠️ 坑:新加的跳数索引对存量数据默认不生效。
ALTER TABLE ... ADD INDEX只写元数据,必须再物化一次,历史 part 才会真正建索引:
sqlALTER TABLE ods.ods_tb_message ADD INDEX idx_from_time (from_account, msg_time) TYPE minmax GRANULARITY 4; ALTER TABLE ods.ods_tb_message MATERIALIZE INDEX idx_from_time; -- 别漏这步物化本身是个 mutation,会跑一阵------回 §3.3 看进度。
八、删数据:三种方式区别很大
| 写法 | 磁盘空间 | 说明 |
|---|---|---|
ALTER TABLE ... DELETE WHERE ... |
8 分钟以后才释放 | 异步 mutation,把命中的 part 逐个重写一遍,旧 part 8 分钟以后才删 |
ALTER TABLE ... DROP PARTITION ... |
当场释放 | 不重写数据,整个分区的文件直接删,最快,能按分区删就别用 DELETE |
TRUNCATE TABLE ... |
当场释放 | 清空整表,和 DROP PARTITION 是一类 |
sql
-- 慢:异步、按条件、要重写 part
ALTER TABLE ods.ods_tb_message DELETE WHERE msg_time < toDate('2026-01-01', 'UTC');
-- 快:直接干掉整个分区
ALTER TABLE ods.ods_tb_message DROP PARTITION '202601';
-- 一次删多个
ALTER TABLE ods.ods_event_log
DROP PARTITION '20260201',
DROP PARTITION '20260202';
为什么同样是「删」,磁盘表现完全不同 ------差别在于要不要重写数据:
#mermaid-svg-80Y3eRa5eWzaLx8t{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-80Y3eRa5eWzaLx8t .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-80Y3eRa5eWzaLx8t .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-80Y3eRa5eWzaLx8t .error-icon{fill:#552222;}#mermaid-svg-80Y3eRa5eWzaLx8t .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-80Y3eRa5eWzaLx8t .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-80Y3eRa5eWzaLx8t .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-80Y3eRa5eWzaLx8t .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-80Y3eRa5eWzaLx8t .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-80Y3eRa5eWzaLx8t .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-80Y3eRa5eWzaLx8t .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-80Y3eRa5eWzaLx8t .marker{fill:#333333;stroke:#333333;}#mermaid-svg-80Y3eRa5eWzaLx8t .marker.cross{stroke:#333333;}#mermaid-svg-80Y3eRa5eWzaLx8t svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-80Y3eRa5eWzaLx8t p{margin:0;}#mermaid-svg-80Y3eRa5eWzaLx8t .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-80Y3eRa5eWzaLx8t .cluster-label text{fill:#333;}#mermaid-svg-80Y3eRa5eWzaLx8t .cluster-label span{color:#333;}#mermaid-svg-80Y3eRa5eWzaLx8t .cluster-label span p{background-color:transparent;}#mermaid-svg-80Y3eRa5eWzaLx8t .label text,#mermaid-svg-80Y3eRa5eWzaLx8t span{fill:#333;color:#333;}#mermaid-svg-80Y3eRa5eWzaLx8t .node rect,#mermaid-svg-80Y3eRa5eWzaLx8t .node circle,#mermaid-svg-80Y3eRa5eWzaLx8t .node ellipse,#mermaid-svg-80Y3eRa5eWzaLx8t .node polygon,#mermaid-svg-80Y3eRa5eWzaLx8t .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-80Y3eRa5eWzaLx8t .rough-node .label text,#mermaid-svg-80Y3eRa5eWzaLx8t .node .label text,#mermaid-svg-80Y3eRa5eWzaLx8t .image-shape .label,#mermaid-svg-80Y3eRa5eWzaLx8t .icon-shape .label{text-anchor:middle;}#mermaid-svg-80Y3eRa5eWzaLx8t .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-80Y3eRa5eWzaLx8t .rough-node .label,#mermaid-svg-80Y3eRa5eWzaLx8t .node .label,#mermaid-svg-80Y3eRa5eWzaLx8t .image-shape .label,#mermaid-svg-80Y3eRa5eWzaLx8t .icon-shape .label{text-align:center;}#mermaid-svg-80Y3eRa5eWzaLx8t .node.clickable{cursor:pointer;}#mermaid-svg-80Y3eRa5eWzaLx8t .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-80Y3eRa5eWzaLx8t .arrowheadPath{fill:#333333;}#mermaid-svg-80Y3eRa5eWzaLx8t .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-80Y3eRa5eWzaLx8t .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-80Y3eRa5eWzaLx8t .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-80Y3eRa5eWzaLx8t .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-80Y3eRa5eWzaLx8t .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-80Y3eRa5eWzaLx8t .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-80Y3eRa5eWzaLx8t .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-80Y3eRa5eWzaLx8t .cluster text{fill:#333;}#mermaid-svg-80Y3eRa5eWzaLx8t .cluster span{color:#333;}#mermaid-svg-80Y3eRa5eWzaLx8t div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-80Y3eRa5eWzaLx8t .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-80Y3eRa5eWzaLx8t rect.text{fill:none;stroke-width:0;}#mermaid-svg-80Y3eRa5eWzaLx8t .icon-shape,#mermaid-svg-80Y3eRa5eWzaLx8t .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-80Y3eRa5eWzaLx8t .icon-shape p,#mermaid-svg-80Y3eRa5eWzaLx8t .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-80Y3eRa5eWzaLx8t .icon-shape .label rect,#mermaid-svg-80Y3eRa5eWzaLx8t .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-80Y3eRa5eWzaLx8t .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-80Y3eRa5eWzaLx8t .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-80Y3eRa5eWzaLx8t :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} DROP PARTITION / TRUNCATE:不重写,当场回收
提交 DROP / TRUNCATE
直接删除
对应的磁盘文件
空间当场释放
ALTER DELETE:要重写,空间 8 分钟以后才回收
提交 DELETE
生成 mutation
进后台队列
逐个 part 重写
parts_to_do 递减
旧 part 变成
active = 0
至少等 old_parts_lifetime
默认 480 秒
删文件,空间释放
💡 看懂这张图,就知道该去哪儿看进度 :卡在
A3就查system.mutations的parts_to_do;过了A3、磁盘还没变小,查system.parts WHERE NOT active看旧 part 还剩多少。能按分区删就别用 DELETE。
⚠️ 三个坑:
磁盘快满时别用
DELETE腾空间 ------新 part 先写出来、旧 part 后删,这期间磁盘反而先涨一截 :在 25.4 上对一张 92 MB 的表删掉约四分之一的行,表目录先涨到 118 MB,8.5~10 分钟后才降到 70 MB。急着回收空间,用DROP PARTITION。先 DELETE 过、再 DROP 同一个分区 ,DROP 掉的部分当场释放,但 DELETE 留下的旧版本 part 还得等满它自己的 8 分钟。看到「DROP 了还有一截没回来」,
system.parts WHERE NOT active里就是它们。删大表 / 大分区可能被安全阈值拦下 (默认 50 GB),报
Table or Partition in ... was not dropped,下一行是Size (...) is greater than max_[table/partition]_size_to_drop。这是 CK 防手滑的保护。确认无误后在当前会话 放开即可:
sqlSET max_table_size_to_drop = 0; -- 0 = 不限制,仅当前会话生效 SET max_partition_size_to_drop = 0;别去改配置文件把它永久关掉------这道保护拦住过很多次误删。
九、速查卡
sql
-- 现在在跑什么
SELECT query_id, user, elapsed, memory_usage, query FROM system.processes ORDER BY elapsed DESC;
KILL QUERY WHERE query_id = '...';
-- 变更任务卡住没
SELECT database, table, mutation_id, is_done, parts_to_do, latest_fail_reason
FROM system.mutations WHERE NOT is_done;
-- 合并在不在动
SELECT database, table, elapsed, progress FROM system.merges;
-- 内存历史 Top(注意 type 过滤;query_log 的 memory_usage 就是峰值)
SELECT query_id, formatReadableSize(memory_usage), query_duration_ms, query FROM system.query_log
WHERE event_date = today() AND type = 'QueryFinish' ORDER BY memory_usage DESC LIMIT 10;
-- 磁盘 Top(注意 active = 1)
SELECT database, table, formatReadableSize(sum(data_compressed_bytes)) FROM system.parts
WHERE active = 1 GROUP BY database, table ORDER BY sum(data_compressed_bytes) DESC LIMIT 20;
-- 分区里堆了多少 part
SELECT partition, count() FROM system.parts WHERE table = 'xxx' AND active GROUP BY partition;
-- 索引有没有生效
EXPLAIN indexes = 1 SELECT ...;
SELECT name, type, expr, granularity FROM system.data_skipping_indices WHERE table = 'xxx';
四条最容易踩的:
- 算磁盘必须
WHERE active = 1,否则把已合并的旧 part 也算进去了。 - 查
query_log必须过滤type,否则一条查询看成两条;它没有peak_memory_usage列 ,memory_usage本身就是峰值。 ALTER ... DELETE是异步的 ,"没生效"先去system.mutations看parts_to_do。- 先查一次
system.*_log占了多少磁盘------它很可能是你集群里最大的那张表。
延伸阅读
本篇负责「出问题该查哪张表 」,下面几篇负责「查出来之后怎么治」:
- 内存报警查到某条 SQL 了,怎么优化 → ClickHouse 内存爆了?一次增量 SQL 从 8.6 GiB 干到 115 MiB 的实战复盘
MemoryTracking一直降不下来,但进程内存正常 → ClickHouse 内存只涨不降之谜:MemoryTracking 20G 但 MemoryResident 才 2.4Gsystem.processes里堆着一大片并发写入 → 1800 行 INSERT 跑了 16 分钟:并发洪流踩坑 + 多账号资源隔离方案- 还没装 / 想把监控一起搭起来 → ClickHouse 25.4 从裸奔到全副武装:Docker 部署 + Prometheus 监控告警 + Grafana 看板一条龙