适用范围与取证边界
本文用于 ClickHouse 查询变慢、内存限制异常和分布式查询放大的定位。以自建 MergeTree 分片为主要场景,参考当前官方 SQL 与系统表文档;先记录实际版本、发起节点、目标表引擎和查询 ID。25.9 起 EXPLAIN indexes 的展示有特别设置要求,旧版本不可直接套用所有新参数。
示例只读取一台已确认节点的系统表或解释一个有限范围的查询计划,未执行生产压测,验证等级为 PENDING。系统日志可能包含业务字面量和身份信息,示例优先提取查询 ID、指标与错误码,完整 SQL 另行脱敏保存。
把客户端等待与服务端耗时分开
用户看到的响应时间包括连接池排队、代理、网络、服务端执行和结果传输。先用 query ID 关联服务端记录,不能拿客户端一秒超时直接推断服务端执行了一秒。超时重试可能让原查询与新查询重叠,进一步放大并发和内存。
分布式查询有发起查询和子查询;比较两个版本时固定数据范围、并发、缓存条件及 SQL 参数。先定位受影响模板,再查几个具体执行实例,避免用所有查询平均值掩盖长尾或把大范围历史分析与日常报表混成一组。
从 Query Log 找到相同对象
SELECT event_time, query_id, initial_query_id, type,
query_duration_ms, read_rows, read_bytes,
memory_usage, exception_code
FROM system.query_log
WHERE event_date >= today() - 1
AND event_time >= now() - INTERVAL 15 MINUTE
AND initial_query_id = 'REPLACE_WITH_INITIAL_QUERY_ID'
AND type IN ('QueryFinish', 'ExceptionWhileProcessing',
'ExceptionBeforeStart')
ORDER BY event_time DESC
LIMIT 20
SETTINGS max_execution_time = 5, max_threads = 2;查询限定已知 ID 和时间窗,同时避免把 QueryStart 与最终事件重复统计。日志存在刷新延迟,采样配置也可能省略部分执行;空结果不能直接证明请求未到达。分布式场景按明确的相关节点逐一取证,initial_query_id 可关联同一执行链中的子查询,但并非所有后台派生任务都保留该链。system.query_log
当前内存与历史记录分别解释
运行中的查询可在目标节点读取当前值及当前峰值,先通过 DESCRIBE TABLE system.processes 核实字段存在。
SELECT query_id, initial_query_id, elapsed,
read_rows, read_bytes, memory_usage, peak_memory_usage
FROM system.processes
WHERE initial_query_id = 'REPLACE_WITH_INITIAL_QUERY_ID'
ORDER BY memory_usage DESC
LIMIT 10
SETTINGS max_execution_time = 5, max_threads = 2;system.processes.memory_usage 是当前查询内存,可能不包含全部专用分配;进程 RSS、后台合并、缓存和并发查询仍需独立看。历史 query_log 的内存字段也不能直接与某一时刻 RSS 相减来求「泄漏」。若 OS 或容器 OOM 杀进程,单条 SQL 日志可能不完整,应关联宿主机记录。system.processes
用 EXPLAIN 核对数据跳过
以下计划示例使用建模文章中的沙箱表,仅用于已经准备好的有限演练数据。实际表不存在时停止,不替换成任意生产大表。
EXPLAIN indexes = 1
SELECT event_type, count()
FROM sandbox.events_design_demo
WHERE tenant_id = 7
AND event_time >= toDateTime('2026-09-01 00:00:00', 'UTC')
AND event_time < toDateTime('2026-09-02 00:00:00', 'UTC')
GROUP BY event_type
SETTINGS use_query_condition_cache = 0,
use_skip_indexes_on_data_read = 0;此处两个查询设置遵循 25.9 及以上版本的官方展示要求;旧版本先核对支持情况,再使用本版本 EXPLAIN 语法。观察过滤前后的 Parts 和 Granules,判断分区、主键与跳过索引是否缩小读取范围。EXPLAIN 不提供真实吞吐证明,索引被使用也不等于足够有效;需要与受控执行记录的读取量对照。EXPLAIN 说明
区分聚合、排序与 JOIN 的放大
高基数 GROUP BY 会保留大量聚合状态,宽列排序需要更多数据和中间结果,JOIN 则可能在构建侧形成大的哈希表。先看过滤是否足够早、连接键是否重复、连接结果是否成倍扩张,再讨论内存参数。
将 JOIN 改成 ANY、改写为 IN、预聚合或使用字典都有语义前提。ANY 会改变多匹配结果,近似去重函数也会改变精度,不能只因更快就替换。JOIN 算法与自动选择能力随版本演进,应以实际计划和官方兼容范围决定,不默认某种算法适用于所有连接类型。JOIN 设计与优化
内存限制与落盘需要一起预算
max_memory_usage 是单服务器上单查询的内存限制,不是整个分布式查询的全局总额。节点上还存在多个查询、后台工作和进程开销;把它提高到机器内存总量容易转成系统 OOM。max_bytes_before_external_group_by 和 max_bytes_before_external_sort 分别控制聚合、排序使用外部存储的相关阈值。
外部聚合或排序会消耗临时磁盘容量和带宽,也不保证所有算子或合并阶段都不再用大量内存。评估前核对临时目录、余量和并发,优先保持 throw 语义,避免使用返回部分结果的 overflow 配置却把不完整数据当作成功报表。查询复杂度限制
从单节点扩展到相关分片
本地 query_log 正常不代表整个 Distributed 查询正常;需要关联发起节点和实际参与分片的记录。读取行数在发起节点与子节点可能存在不同汇总口径,不应全部相加形成一个虚假的总扫描量。慢分片、网络传输或发起端最终聚合都可能成为瓶颈。
只选择对应查询实际访问的节点,避免在事故中对全部副本做无界日志扫描。核对是否启用了跳过不可用分片等设置,返回成功也可能不符合业务完整性要求。后台合并和查询争用资源时,应结合已有时间线判断,不能通过停止所有合并制造短暂更快的查询。Distributed 引擎
验收、常见误区与停止回退
使用固定结果样本比较变更前后值、行数和精度,并比较代表性并发下的延迟分布、读取量、内存、临时磁盘与错误率。查询 LIMIT 通常限制输出,不保证前面的聚合或排序只处理少量数据;时间与读取限制也不是操作系统级硬隔离,需保留观察和取消渠道。
常见误区是只加内存、只看第一次冷查询、把当前使用量当作整次峰值,以及以返回部分结果消除报错。若临时磁盘接近预算、出现新 OOM 或结果不一致,停止试验并恢复原 SQL 或查询级设置。保留 query ID、计划和配置差异;已落地的错误结果需独立撤回或重算,不能只让下一次查询成功就结案。
相关阅读:MergeTree 数据设计、副本与备份运维。