📋 版本适用性声明
本指南旨在提供一个跨版本通用的ClickHouse内存优化方法论。核心优化原则(如减少主键数量、控制写入频次、限制查询内存)适用于所有主流版本。然而,具体配置参数、系统表字段和某些高级功能的可用性会因版本而异。
主要版本功能支持概览
| 功能模块 | 20.x - 22.x LTS | 23.x (含您的23.8) | 24.x 及以上 | 核心建议 |
|---|---|---|---|---|
主键缓存 (primary_key_cache) |
部分支持,行为有差异 | 完整支持,需显式开启 | 完整支持,且持续优化 | v23.8适用。这是您版本的关键优化点。 |
系统监控表字段 (如system.mutations) |
字段较少 | 字段不完整,如缺duration_ms |
字段最全,监控能力最强 | v23.8需使用替代字段,下文会提供专属查询。 |
system.caches 表 |
不存在 | 不存在 | 存在 | v23.8需通过异步指标间接观察缓存。 |
| 资源组(Resource Groups) | 基础支持 | 功能增强 | 功能完善 | v23.8可用,用于实现资源隔离。 |
| 通配符与函数 | 基础支持 | 支持更完善的通配符和聚合函数 | 完全支持 | v23.8的查询语法基本现代化。 |
结论:您使用的 v23.8 是一个承上启下的版本,核心优化功能均已具备,但部分监控便捷性不如24.x及以上版本。本指南将特别标注适用于您版本的方案。
🔍 第一阶段:评估与监控(v23.8 特别适配)
在优化前,必须先定位内存消耗点。以下查询已针对 v23.8 调整。
1. 全局内存概况
-- 查看ClickHouse进程整体内存 (v23.8 适用)
SELECT * FROM system.asynchronous_metrics
WHERE metric LIKE '%Memory%' OR metric LIKE '%Cache%';
-- 查看当前内存分配器状态 (关键!)
SELECT * FROM system.metrics
WHERE metric IN ('MemoryTracking', 'MarkCacheBytes', 'UncompressedCacheBytes');
2. 定位内存消耗最大的表(核心)
-- 查看各表主键索引内存、parts数量 (v23.8 完全支持)
SELECT
database,
table,
formatReadableSize(sum(primary_key_bytes_in_memory)) AS pk_memory,
count() AS parts_count,
formatReadableSize(sum(data_uncompressed_bytes)) AS uncompressed_size
FROM system.parts
WHERE active = 1
GROUP BY database, table
ORDER BY pk_memory DESC
LIMIT 20;
3. 监控正在进行的危险操作
-- 查看正在进行的Merge操作及内存使用 (v23.8 适用)
SELECT
database,
table,
elapsed,
formatReadableSize(memory_usage) AS memory_used,
progress
FROM system.merges
WHERE progress < 1
ORDER BY memory_usage DESC;
-- 查看近期的突变(Mutation,如TTL、DELETE)(v23.8 适用,使用现有字段)
SELECT
database,
table,
mutation_id,
command,
create_time,
parts_to_do,
is_done
FROM system.mutations
WHERE is_done = 0
ORDER BY create_time DESC;
4. 识别高内存查询
-- 从查询日志中找出高内存消耗的查询 (v23.8 适用,注意无`elapsed`字段)
SELECT
query_id,
user,
substring(query, 1, 150) as short_query,
formatReadableSize(memory_usage) AS memory_used,
read_rows,
written_rows,
query_start_time
FROM system.query_log
WHERE event_date = today()
AND memory_usage > 1_000_000_000 -- 查找使用超过1GB内存的查询
AND type = 'QueryFinish'
ORDER BY memory_usage DESC
LIMIT 10;
⚙️ 第二阶段:配置与参数优化(版本分级配置)
1. 内存限制配置(所有版本核心)
在 users.xml 或 profiles 中为不同用户或查询设置硬限制。
<!-- 示例:为 default 用户设置严格限制 (v20.x, 23.x, 24.x 均支持) -->
<profiles>
<default>
<!-- 单个查询内存限制,建议设置为总内存的 10%-20% -->
<max_memory_usage>10000000000</max_memory_usage> <!-- 10GB -->
<!-- 所有查询总内存限制,防止并发拖垮系统 -->
<max_memory_usage_for_user>50000000000</max_memory_usage_for_user> <!-- 50GB -->
<!-- 启用当内存超限时,将GROUP BY溢出到磁盘 -->
<max_bytes_before_external_group_by>2000000000</max_bytes_before_external_group_by>
<!-- 聚合时优化内存使用 -->
<group_by_two_level_threshold>100000</group_by_two_level_threshold>
</default>
</profiles>
2. 主键索引优化(v23.8+ 重点)
这是降低常驻内存最有效的手段。
-- 为指定大表启用主键缓存 (v23.8 必需手动开启)
ALTER TABLE your_large_table
MODIFY SETTING use_primary_key_cache = 1,
primary_index_cache_size = 1073741824; -- 为此表分配 1GB 缓存空间
-- 设置全局主键缓存总大小 (在 config.xml 中)
<primary_index_cache_size>10737418240</primary_index_cache_size> <!-- 10GB -->
-- 修改索引粒度以减少索引条目数 (所有版本支持)
ALTER TABLE your_table MODIFY SETTING index_granularity = 16384; -- 从默认8192调大
3. 合并(Merge)策略优化
控制后台Merge操作的内存和频率,避免集中爆发。
<!-- 在 config.xml 的 merge_tree 部分调整 (v20.x+ 支持) -->
<merge_tree>
<!-- 最大合并线程数,根据CPU核心数调整 -->
<max_merge_threads>8</max_merge_threads>
<!-- 控制待合并数据的总大小,避免超大合并 -->
<max_bytes_to_merge_at_max_space_in_pool>107374182400</max_bytes_to_merge_at_max_space_in_pool>
<!-- 限制同时进行的TTL Merge数量 -->
<max_number_of_merges_with_ttl_in_pool>1</max_number_of_merges_with_ttl_in_pool>
</merge_tree>
🛠️ 第三阶段:表设计与写入优化(版本通用最佳实践)
1. 优化的表结构设计
CREATE TABLE optimized_table (
-- 使用低基数、短类型字段作为主键前缀
event_date Date,
user_id UInt32,
-- 高基数字段放在主键后面或使用索引
event_id UUID,
-- 避免在ORDER BY中使用长字符串
...
) ENGINE = MergeTree()
-- 分区键选择:时间范围,避免产生过多分区
PARTITION BY toYYYYMM(event_date)
-- 主键设计:选择查询最常用的过滤条件,列数不宜过多(2-4列为佳)
ORDER BY (event_date, user_id)
-- 关键设置:
SETTINGS
index_granularity = 16384, -- 增大颗粒度
min_bytes_for_wide_part = 104857600, -- 100MB以上使用宽存储
ttl_only_drop_parts = 1; -- TTL时整块删除parts,减少负载
2. 智能数据生命周期管理(TTL)
-- 自动清理旧数据,减少数据量是根本
ALTER TABLE your_table
MODIFY TTL
event_time + INTERVAL 90 DAY TO VOLUME 'cold',
event_time + INTERVAL 180 DAY DELETE;
-- 定期手动合并,优化存储
OPTIMIZE TABLE your_table FINAL;
3. 写入模式优化
-
批量写入:单次插入至少 10万行 或 64MB 数据。
-
控制频率:避免高频(如每秒)的小批量写入,建议间隔 1分钟以上。
-
异步写入:使用
Buffer表或外部队列(如Kafka)进行缓冲。
-- 使用Buffer表平滑写入峰值 (所有版本支持)
CREATE TABLE your_table_buffer AS your_table
ENGINE = Buffer('default', 'your_table', 16, 10, 30, 10000, 1000000, 10000000, 100000000);
🔎 第四阶段:查询优化(避免“读”引发内存爆炸)
1. 为高内存查询添加防护
-- 在查询前设置会话级限制 (v23.8 适用)
SET max_memory_usage = 5000000000; -- 本次查询最多用5GB
SET max_bytes_before_external_sort = 2000000000; -- 排序溢出到磁盘的阈值
-- 示例:一个可能消耗大量内存的GROUP BY查询
SELECT
user_id,
count()
FROM large_table
GROUP BY user_id
SETTINGS
optimize_aggregation_in_order = 1, -- 按主键顺序优化聚合
max_bytes_before_external_group_by = 2000000000;
2. 避免常见内存陷阱
-
禁用
DISTINCT:在大数据集上使用DISTINCT极易OOM,改用近似函数uniqCombined。 -
慎用
JOIN:特别是将大表放在JOIN的右侧,应将小表放右侧。使用GLOBAL IN代替分布式JOIN。 -
限制结果集:总是使用
LIMIT,尤其是交互式查询。
🚨 第五阶段:紧急处理与预防(v23.8 操作清单)
当内存使用率突然飙升,即将触发OOM时,按顺序执行:
-
立即终止危险查询:
-- 查找正在运行的高内存查询 SELECT query_id, user, query, memory_usage FROM system.processes WHERE memory_usage > 5000000000 ORDER BY memory_usage DESC; -- 终止指定查询 KILL QUERY WHERE query_id = 'query_id_here'; -
释放缓存(治标不治本):
SYSTEM DROP MARK CACHE; SYSTEM DROP UNCOMPRESSED CACHE; -
暂停后台Merge(极端情况):
-- 通过修改表设置临时降低Merge积极性 ALTER TABLE problem_table MODIFY SETTING merge_with_ttl_timeout = 3600; -- 延长TTL合并间隔
📈 第六阶段:长期监控与告警
为以下指标建立仪表盘和告警(以Prometheus为例):
| 监控指标 | 告警阈值(建议) | 说明 |
|---|---|---|
| ClickHouse进程内存使用 | > 总内存的 85% | 最核心的告警。 |
| 单个查询内存使用 | > 5GB | 找出需要优化的查询。 |
| Parts数量 | 单表 > 1000 | Parts过多影响Merge和查询性能。 |
| Merge操作内存 | > 10GB | 可能正在进行超大合并。 |
💎 总结与版本升级建议
对于 v23.8 用户,您可以立即实施本指南中 90% 的优化措施。您与最新版本的主要差距在于监控的便捷性和部分自动化管理功能。
长期建议:规划升级到 24.x 以上的LTS版本。新版本在内存管理上更为精细和智能,例如:
-
更完善的
system.caches和system.metric_log。 -
查询队列和资源组功能更成熟。
-
后台任务自动调优能力更强。
通过 “配置优化打基础 + 监控分析找重点 + 查询与写入规范避风险” 的组合拳,您完全可以驯服ClickHouse的内存使用,使其在高效运行的同时保持稳定。
posted on
浙公网安备 33010602011771号