clickhouse-query-memory

ClickHouse 查询报 MEMORY_LIMIT_EXCEEDED(Code 241),或把节点内存打满后被 OOM Killer 终止。

现象

查询直接失败并返回 Memory limit (for query) exceeded 或 Memory limit (total) exceeded,异常码 241;也可能节点 RSS 飙升后被内核 OOM Kill,system.query_log 里留下未正常结束的查询。常见于大聚合、大 JOIN 和排序查询。

快速定位

先让日志落盘,再看失败查询的峰值内存:

clickhouse-client --query "SYSTEM FLUSH LOGS"
clickhouse-client --query "SELECT event_time, query_id, user, formatReadableSize(peak_memory_usage) AS peak, formatReadableSize(memory_usage) AS mem, read_rows, result_rows, exception_code, substring(query, 1, 100) FROM system.query_log WHERE event_time > now() - INTERVAL 2 HOUR AND exception_code = 241 ORDER BY event_time DESC LIMIT 10 FORMAT Vertical"

再看当前正在运行的查询:

clickhouse-client --query "SELECT query_id, elapsed, formatReadableSize(memory_usage) AS mem, read_rows, substring(query, 1, 120) FROM system.processes ORDER BY memory_usage DESC LIMIT 10"
clickhouse-client --query "SELECT metric, value FROM system.metrics WHERE metric IN ('MemoryTracking', 'Query', 'QueryThread')"

核对查询限额与服务端上限:

clickhouse-client --query "SELECT name, value FROM system.settings WHERE name IN ('max_memory_usage', 'max_memory_usage_for_user', 'max_threads', 'max_bytes_before_external_group_by', 'max_bytes_before_external_sort', 'max_block_size', 'join_algorithm')"
clickhouse-client --query "SELECT name, value FROM system.server_settings WHERE name LIKE 'max_server_memory%'"

system.query_log 里的 peak_memory_usage 才是关键,system.processes.memory_usage 只是当前值。若查询已结束,先 SYSTEM FLUSH LOGS 再查,否则看不到最新记录。

易误判:把 max_memory_usage 设为 0 只是取消单查询上限,仍受 max_server_memory_usage(默认约 0.9×RAM)和用户配额约束;报错的那条查询也未必是元凶 —— 并发查询合计可能先触发服务端总限额。

常见根因

  1. 高基数 GROUP BY/DISTINCT,聚合状态按 key 常驻内存。
  2. 大表 JOIN:右表构建哈希表,表大或连接顺序不当导致内存暴涨。
  3. 无 LIMIT 的大排序,或聚合后才排序;外部排序未启用。
  4. max_threads 过大,每线程缓冲和块叠加放大单查询内存。
  5. SELECT *、缺少分区裁剪、未用 PREWHERE,中间结果过大。
  6. FINAL、窗口函数、arrayJoin 放大行数或触发额外合并。
  7. 并发查询叠加,合计超过服务端总内存限制。
  8. 外部 GROUP BY/SORT 阈值设得过高,落盘后的合并阶段仍然爆内存。

修复

相关命令

读完自测

  1. 为什么 max_memory_usage=0 仍可能触发内存限制?
  2. 定位内存峰值应看 memory_usage 还是 peak_memory_usage?
  3. 大聚合查询最先可以调整哪两个设置来避免爆内存?
答案
  1. 单查询上限取消后,仍受服务端 max_server_memory_usage(默认约 0.9×RAM)、用户配额和并发合计限制。
  2. 看 peak_memory_usage;memory_usage 只是采样时的当前值,会漏掉瞬时峰值。
  3. max_threads 和 max_bytes_before_external_group_by(排序场景再加 max_bytes_before_external_sort)。