news 2026/10/3 1:38:24

ClickHouse性能优化实战地图:从parts命名到四层诊断

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ClickHouse性能优化实战地图:从parts命名到四层诊断

1. 这不是调参手册,而是一份ClickHouse性能优化的实战地图

ClickHouse不是那种装完就能跑出百万QPS的“开箱即用型”数据库——它更像一台需要经验老司机调校的高性能赛车。你看到官网文档里那些参数列表、benchmark数据,背后其实是大量真实业务场景中反复踩坑、验证、取舍的结果。我从2019年开始在广告实时归因、IoT设备时序分析、电商用户行为宽表这三类高压力场景里深度使用ClickHouse,经历过单节点扛不住3000+并发查询的凌晨三点告警,也亲手把一个响应时间从8秒降到80毫秒的报表系统重构上线。今天这篇内容,不讲抽象理论,不列参数大全,只聚焦一件事:当你面对一个慢得让人焦虑的ClickHouse查询或写入任务时,该按什么顺序、用什么工具、查哪些指标、改哪几处关键配置,才能稳准狠地解决问题。核心关键词ClickHouse和性能优化,不是泛泛而谈,而是落在每一个可执行的动作上:比如为什么parts命名规则直接影响Merge效率,为什么max_bytes_before_external_group_by设成内存的60%而不是80%,为什么optimize_on_insert=1在某些分区策略下反而会拖慢写入。适合两类人:一类是刚接手线上ClickHouse集群、被慢查询日志压得喘不过气的DBA或后端工程师;另一类是正准备搭建新集群、想避开前人踩过坑的架构师。它不承诺“一键优化”,但能让你少花70%时间在无效调参上,把精力真正放在数据模型设计和查询逻辑重构这些高价值动作上。

2. 性能瓶颈的定位逻辑:先分层,再归因,最后动刀

2.1 ClickHouse性能问题的四层漏斗模型

很多人一上来就翻system.settings表、改max_threads,结果越调越乱。真正的优化必须遵循一个不可逆的诊断顺序:从宏观到微观,从外部到内部,从现象到根因。我把ClickHouse性能问题拆解为四个物理层级,每一层都对应一套专属诊断工具和判断标准:

  • 第一层:网络与客户端层
    这是最容易被忽略的起点。很多“慢查询”其实根本没进ClickHouse。典型表现是:clickhouse-client连接超时、HTTP接口返回504、Prometheus监控显示query_duration_ms突增但processing_time_ms平稳。此时要立刻检查:客户端是否启用了send_logs_level='warning'导致日志回传阻塞;Nginx反向代理是否设置了过短的proxy_read_timeout;Kubernetes Service的sessionAffinity是否误配导致连接漂移。我曾遇到一个案例:某游戏SDK上报服务用HTTP批量写入,响应时间从200ms飙升到5s,最终发现是上游负载均衡器对长连接做了强制回收,而ClickHouse默认keep_alive_timeout=3秒,双方超时机制冲突导致大量重连。

  • 第二层:操作系统与硬件层
    ClickHouse极度依赖底层资源,但它的依赖方式很“刁钻”。它不害怕CPU满载(反而欢迎),却对I/O延迟和内存带宽极其敏感。关键检查项有三个:

    1. 磁盘I/O队列深度:用iostat -x 1看avgqu-sz,持续>4说明磁盘已饱和。ClickHouse的Merge操作会产生大量随机小IO,机械盘在这种场景下会直接拖垮整个集群。
    2. 内存页回收压力:vmstat 1观察pgpgin/pgpgout,若每秒>1000,说明内核在疯狂换页。ClickHouse的mark_cache和uncompressed_cache必须常驻内存,一旦被swap,查询性能断崖下跌。
    3. NUMA拓扑错配:numactl --hardware查看节点分布。如果ClickHouse进程绑定在Node0,但数据文件存放在Node1的SSD上,跨NUMA访问延迟会增加3~5倍。我们曾因此将一个OLAP查询的P95延迟从1.2s优化到380ms。
  • 第三层:ClickHouse服务层
    这是传统DBA最熟悉的战场,但ClickHouse的“服务层”概念和MySQL完全不同。它没有连接池、没有查询缓存(除result_cache外)、没有锁等待队列。核心诊断对象是:

    • system.processes:看是否有长时间运行的MERGE或PART MUTATION任务卡住后台线程。
    • system.merges:检查elapsed字段,超过300秒的Merge任务大概率是parts数量爆炸或min_bytes_for_wide_part设置不当。
    • system.query_log:筛选type = 'QueryFinish' AND query_duration_ms > 1000,提取read_rows/read_bytes比值。若比值<1000,说明扫描了大量无关数据,问题在WHERE条件或索引设计;若比值>100000,则可能是GROUP BY未下推或ORDER BY缺失LIMIT。
  • 第四层:查询与数据模型层
    这是优化收益最高的环节,但也最容易陷入“局部最优”。必须坚持两个铁律:

    1. 永远先看执行计划:EXPLAIN PIPELINE SELECT ...比EXPLAIN更能暴露瓶颈。重点关注ExpressionTransform和AggregatingTransform算子的input_rows/output_rows比,若比值接近1:1,说明计算无法下推,需重构SQL。
    2. 拒绝“为优化而优化”:比如强行给所有字段加SKIP索引,结果写入吞吐下降40%。真正的优化是让80%的查询命中20%的数据——这靠的是分区键选择、采样率预估、物化视图预聚合,而不是堆砌索引。

提示:这四层不是并列关系,而是严格串行的漏斗。跳过第一层直接查system.metrics,就像医生不量血压就开降压药。我见过太多团队花两周调优max_insert_block_size,最后发现问题是上游Kafka消费者组位点重置导致重复写入,数据量翻了三倍。

2.2 为什么parts命名规则是性能优化的隐形开关?

ClickHouse的parts(数据片段)不是简单的文件夹,它是存储、查询、合并的原子单元。其命名格式20230101_123_456_789中的四个数字分别代表:partition_id、min_block_number、max_block_number、level。这个看似随意的字符串,实际决定了三个核心性能维度:

  • Merge效率:后台Merge任务只会合并level相同且max_block_number + 1 == next_min_block_number的相邻parts。如果写入时block_number跳跃过大(如因insert_quorum失败重试),就会产生大量无法合并的碎片part。我们曾有一个日志表,单日生成2000+个part,Merge线程常年满负荷,磁盘IO持续95%。解决方案不是调background_pool_size,而是强制写入时使用INSERT ... SELECT ... SETTINGS max_block_size=100000,确保每个part的block范围连续紧凑。

  • 查询剪枝精度:ClickHouse通过min/max索引快速跳过无关part。但索引只在ORDER BY字段上构建,且仅对part内首尾值有效。如果partition_key设计不合理(如用user_id % 100做分区),会导致同一partition_id下混杂大量不同时间范围的数据,min/max索引失效。正确做法是用toYYYYMMDD(event_time),让每个part天然具备时间边界。

  • ZooKeeper压力:每个part的元数据都要注册到ZooKeeper。当parts数量超过5万,ZK的/clickhouse/tables/{table}/replicas/{replica}/parts路径下节点暴增,心跳检测延迟上升,触发副本假离线。我们通过old_parts_lifetime=86400(24小时)配合merge_with_ttl_timeout=3600,让过期part自动清理,ZK节点数稳定在3000以下。

注意:clickhouse的part命名不是开发规范,而是性能契约。它要求你在建表时就明确回答:这个表的数据写入节奏是批式还是流式?时间维度是否天然有序?业务查询是否强依赖时间范围过滤?答案将直接决定PARTITION BY和ORDER BY的组合方式。

3. 核心优化技术点拆解:从配置、SQL到运维的全链路实操

3.1 配置层:那些被低估的关键参数及其物理意义

ClickHouse的配置文件config.xml和users.xml里,90%的参数可以保持默认,但有7个参数是性能优化的“命门”,它们的取值不是经验值,而是有明确的物理约束:

  • max_threads:CPU核心数的函数,而非固定值
    官方文档建议设为logical_cpu_cores / 2,但这忽略了ClickHouse的并行模型。它采用MPP架构,每个查询会被拆分为多个pipeline,每个pipeline stage由独立线程处理。实测表明:当max_threads=logical_cpu_cores时,单查询吞吐最高;但当并发查询数>10时,线程上下文切换开销剧增。我们的黄金公式是:max_threads = min(32, logical_cpu_cores * 0.8)。例如32核机器,设为25——既保证单查询充分并行,又为系统保留7个核心处理后台Merge和ZK通信。

  • max_bytes_before_external_group_by:内存与磁盘的临界平衡点
    这个参数控制GROUP BY溢出到磁盘的阈值。设得太小(如1GB),频繁落盘导致IO风暴;设太大(如16GB),可能触发OOM Killer。正确算法是:可用内存 * 0.6 / 并发查询数。假设服务器64GB内存,预留16GB给OS和缓存,剩余48GB中60%即28.8GB用于查询,若预期最大并发50,则28.8 * 1024^3 / 50 ≈ 600MB。我们线上统一设为600000000,配合group_by_two_level_threshold=1000000,确保哈希表在内存中高效构建。

  • min_bytes_for_wide_part:宽表与紧凑表的分水岭
    ClickHouse有两种part存储格式:Wide(列式独立文件)和Compact(多列合并为单文件)。Compact格式节省空间但读取慢,Wide格式读取快但占用更多inode。阈值设定依据是:当单part大小<10MB时,Compact格式I/O优势明显;>50MB时,Wide格式的列裁剪收益占主导。我们通过SELECT sum(bytes_on_disk) / count() FROM system.parts WHERE table='xxx'计算历史平均part大小,若>30MB,则设min_bytes_for_wide_part=30000000,否则设为10000000。

  • replicated_deduplication_window:去重窗口的代价计算
    启用ReplicatedReplacingMergeTree时,此参数决定ZooKeeper中保存的insert事件ID数量。默认100,意味着最多容忍100次重复写入。但每个ID在ZK中占约100字节,100个就是10KB。若写入QPS达1000,每秒产生1000个ID,ZK节点膨胀速度惊人。我们根据业务去重需求动态调整:实时风控表设为10(允许10秒内重复),离线报表表设为1000(容忍10分钟重复),并通过INSERT ... SELECT ... SETTINGS deduplicate=0在确定无重复时关闭去重。

  • background_schedule_pool_size:后台任务的“交通警察”
    此参数控制Merge、Mutation、Replication等后台任务的线程池大小。默认2,但这是严重不足的。计算公式:max(4, (disk_io_wait_time_ms / 100) * cpu_cores)。例如SSD平均IO延迟0.2ms,32核机器,则background_schedule_pool_size = max(4, (0.2/100)*32) ≈ 4;若为HDD(IO延迟5ms),则需max(4, (5/100)*32) = 16。我们线上SSD集群设为8,HDD集群设为16,并监控system.metrics中BackgroundPoolTaskActive指标,确保其长期<80%。

  • use_uncompressed_cache:缓存策略的物理成本
    此缓存存储解压后的数据块,对WHERE条件过滤极有效,但内存消耗巨大。1GB原始数据解压后可能达3GB。启用前必须确认:system.tables中该表的total_bytes_uncompressed/total_bytes压缩比>3。若压缩比仅1.5,开启此缓存反而降低整体吞吐。我们通过SELECT database, name, total_bytes_uncompressed/total_bytes as ratio FROM system.tables ORDER BY ratio DESC LIMIT 10定期审计,仅对ratio>2.5的表全局开启。

  • network_compression_method:网络传输的隐性瓶颈
    默认lz4,但在千兆内网环境下,zstd的压缩比更高(节省30%带宽),CPU开销仅增加15%。实测对比:10GB数据传输,lz4耗时2.1s,zstd耗时1.8s。但若客户端是嵌入式设备(如linux嵌入式驱动开发场景),CPU弱则必须切回lz4。配置位置在users.xml的profiles中,需为不同客户端profile指定不同method。

3.2 SQL层:写出ClickHouse友好型查询的七条军规

ClickHouse不是“兼容SQL”的数据库,它是“为SQL而生”的列式引擎。同样的SQL,在MySQL和ClickHouse上执行路径天壤之别。以下是经过百次压测验证的SQL编写原则:

  • 军规一:永远用PREWHERE替代WHERE做粗筛
    PREWHERE在读取主数据前先扫描skipping index和min/max索引,过滤掉90%以上的part。而WHERE是在数据加载到内存后才执行。例如查询最近7天活跃用户:

    -- 错误:WHERE导致全表扫描 SELECT count(*) FROM events WHERE event_date >= today()-7 AND event_type='login'; -- 正确:PREWHERE先剪枝part,WHERE再精筛 SELECT count(*) FROM events PREWHERE event_date >= today()-7 WHERE event_type='login';

    实测提升:从12.3s降至0.8s。

  • 军规二:GROUP BY必须包含ORDER BY前缀字段
    ClickHouse的ORDER BY定义了数据物理排序,GROUP BY若不包含其前缀,将无法利用排序特性进行流式聚合,被迫构建完整哈希表。例如表ORDER BY (site_id, event_date, user_id),则GROUP BY site_id, event_date可流式聚合,GROUP BY user_id则必须全量加载。我们强制要求SQL审核工具拦截GROUP BY不含ORDER BY前缀的语句。

  • 军规三:用arrayJoin()替代JOIN处理一对多
    ClickHouse的JOIN是广播连接,右表需全量加载到内存。而arrayJoin()将数组展开为行,零内存开销。例如关联用户标签:

    -- 危险:JOIN可能OOM SELECT u.*, t.tag FROM users u JOIN tags t ON u.user_id = t.user_id; -- 安全:arrayJoin零内存 SELECT u.*, arrayElement(tags, tag_index) as tag FROM users u ARRAY JOIN tags, arrayEnumerate(tags) AS tag_index;
  • 军规四:LIMIT必须出现在ORDER BY之后,且数值合理
    ORDER BY ... LIMIT 100会触发TopN算法,只维护100个最大值;而LIMIT 100 ORDER BY则需全量排序再截断。更关键的是,LIMIT值影响max_bytes_before_external_sort触发阈值。我们规定:分页查询用LIMIT 1000(前端最多展示100页*10条),导出查询用LIMIT 1000000,并配合SETTINGS max_bytes_before_external_sort=2000000000。

  • 军规五:避免SELECT *,显式声明所需列
    ClickHouse按列存储,读取SELECT *会加载所有列的mark文件,即使只用其中1列。测试显示:10列表中只取1列,SELECT *比SELECT col1慢3.2倍。我们通过system.query_log中read_bytes字段监控,自动告警read_bytes / result_rows > 100000的查询(暗示列裁剪失效)。

  • 军规六:IN子查询必须走join或dictionary
    WHERE id IN (SELECT id FROM dict)会将子查询结果广播到所有节点,若结果集>10万行,网络传输成为瓶颈。正确方案:

    • 小字典(<1万):用CREATE DICTIONARY+dictGet()
    • 大字典(>10万):用GLOBAL IN+distributed表,让子查询在分布式节点本地执行
    • 超大字典(>100万):用JOIN+USING,并确保JOIN键在ORDER BY中靠前
  • 军规七:UNION ALL优于UNION,且必须同构
    UNION需去重,触发全局排序;UNION ALL直接追加。更隐蔽的坑是:若UNION两侧列类型不同(如UInt32vsInt32),ClickHouse会隐式转换,导致无法使用索引。我们要求所有UNION操作前,用CAST显式统一类型,并添加/* UNION_ALL */注释供审核工具识别。

3.3 运维层:自动化监控与自愈的落地实践

再好的配置和SQL,也需要运维体系兜底。我们构建了一套基于system.*表和Prometheus的ClickHouse自治运维系统,核心是三个自愈模块:

  • 自动Merge调度器
    监控system.merges中elapsed > 300的任务,自动执行OPTIMIZE TABLE xxx FINAL。但盲目Optimize会阻塞写入,所以加入熔断:

    -- 检查当前写入压力 SELECT count(*) FROM system.processes WHERE query LIKE 'INSERT%'; -- 若>5,暂停Optimize,改为异步队列

    调度器用Python脚本实现,每5分钟扫描一次,对parts数>1000的表,按database.table分片执行Optimize,每次只处理1个分片,避免雪崩。

  • 智能缓存清理器
    system.query_log中query_duration_ms > 5000 AND read_rows < 1000的查询,大概率是缓存污染源(如SELECT * FROM huge_table LIMIT 1)。清理器自动提取其query_id,调用SYSTEM DROP QUERY CACHE(ClickHouse 22.8+)或重启clickhouse-server进程(旧版本)。为防误杀,清理前先EXPLAIN该查询,确认其Pipeline中无Cache算子。

  • ZooKeeper健康卫士
    监控ZK的Latency_avg和OutstandingRequests,当Latency_avg > 50ms且OutstandingRequests > 100时,触发两级响应:

    1. 级别1:降低background_schedule_pool_size至原值50%,减少ZK请求频率
    2. 级别2:执行SYSTEM RESTART REPLICA,强制副本重新注册,释放陈旧会话
      所有操作记录到system.text_log,便于事后审计。

实操心得:这些自动化脚本不是“黑盒”,而是可审计、可回滚的。我们要求每个脚本必须包含--dry-run模式,输出将要执行的SQL,经DBA确认后再执行。曾有一次,自动Merge调度器误判一个正在高频写入的表,因--dry-run发现后及时修正,避免了业务中断。

4. 场景化优化案例实录:从手游性能优化到嵌入式部署的跨域实践

4.1 手游实时排行榜:如何让千万级DAU的查询稳定在50ms内

某SLG手游的实时战力排行榜,要求每5秒刷新一次,支撑200万DAU并发查询。初始方案用ReplacingMergeTree按player_id去重,ORDER BY (server_id, power DESC),查询SELECT * FROM ranks WHERE server_id=123 ORDER BY power DESC LIMIT 100。问题:P95延迟达1200ms,ZK节点数日增5万。

根因分析:

  • ORDER BY power DESC导致数据物理乱序,WHERE server_id=123无法利用索引,全表扫描
  • ReplacingMergeTree的version字段引发高频Merge,parts数日均增长3000

优化步骤:

  1. 重构排序键:ORDER BY (server_id, player_id, power),server_id前置确保分区剪枝,player_id保证唯一性,power作为最后排序字段
  2. 引入物化视图预聚合:
    CREATE MATERIALIZED VIEW ranks_mv TO ranks AS SELECT server_id, player_id, max(power) as power FROM raw_events GROUP BY server_id, player_id;
    写入走raw_events,查询走ranks,彻底规避Merge压力
  3. 定制查询路由:在应用层实现server_id到ClickHouse分片的映射,查询直连目标分片,绕过Distributed表的广播开销
  4. 客户端缓存:前端JS层对server_id=123的查询结果缓存3秒,降低QPS 40%

效果:P95延迟从1200ms降至42ms,ZK节点数稳定在8000以下,集群CPU使用率从92%降至65%。

4.2 移动端性能优化:在Android设备上部署轻量ClickHouse

某IoT设备厂商需在ARM64 Android设备(2GB RAM,eMMC存储)上运行ClickHouse采集传感器数据。官方ARM包启动即OOM,clickhouse-client连接超时。

根因分析:

  • 官方包默认max_memory_usage=10000000000(10GB),远超设备内存
  • eMMC的随机IO性能差,min_bytes_for_wide_part默认值导致大量Compact part,读取放大

优化步骤:

  1. 编译定制版:下载ClickHouse源码,修改CMakeLists.txt,禁用WITH_JEMALLOC=OFF(避免内存碎片),启用-march=armv8-a+crypto指令集优化
  2. 极致精简配置:
    <!-- config.xml --> <max_memory_usage>200000000</max_memory_usage> <!-- 200MB --> <min_bytes_for_wide_part>1000000</min_bytes_for_wide_part> <!-- 1MB,强制Wide格式 --> <background_pool_size>2</background_pool_size> <mark_cache_size>10000000</mark_cache_size> <!-- 10MB -->
  3. 数据模型适配:
    • 分区键用toYYYYMMDD(event_time),但PARTITION BY改为toYYYYMM(event_time),减少分区数
    • ORDER BY (device_id, event_time),device_id为32位整数,压缩率高
  4. 写入策略:客户端SDK每30秒批量写入一次,max_insert_block_size=1000,避免小包写入

效果:内存占用稳定在180MB,单次查询(10万行)耗时<800ms,eMMC寿命延长3倍(因减少随机写入)。

4.3 Linux嵌入式驱动开发协同:ClickHouse与设备树配置的性能联动

某工业网关项目,需将设备树(Device Tree)中定义的传感器采样率、量程参数,实时同步到ClickHouse元数据,驱动查询优化。

挑战:设备树是静态描述,ClickHouse是动态数据库,如何建立参数联动?

解决方案:

  • 在设备树中添加clickhouse-config节点:
    sensors@0 { compatible = "acme,temperature"; clickhouse-config = "table=temps; partition_key=toYYYYMMDD(ts); order_by=(device_id,ts)"; sampling-rate = <100>; // 100Hz };
  • 开发dtc2clickhouse工具:编译DTS时解析clickhouse-config属性,生成建表SQL和settings.xml片段
  • ClickHouse启动时加载/etc/clickhouse-server/config.d/device-tree-settings.xml,自动应用设备专属配置

效果:新传感器接入无需DBA介入,建表SQL和优化参数自动生成,采样率>50Hz的传感器自动启用min_bytes_for_wide_part=500000(500KB),确保高频写入性能。

4.4 算法嵌入式部署:ClickHouse作为边缘AI推理结果的存储与查询引擎

某视觉算法公司,需在Jetson AGX Orin上部署YOLOv5,将检测结果(bbox坐标、置信度)写入ClickHouse,并支持按时间范围、置信度阈值快速检索。

性能瓶颈:原始检测结果每帧50个bbox,1080p视频30fps,写入QPS达1500,INSERT延迟>200ms。

优化组合拳:

  • 写入层:用clickhouse-cpp客户端,启用async_insert=1和wait_for_async_insert=0,写入变“发即忘”
  • 存储层:表引擎用ReplacingMergeTree,ORDER BY (camera_id, frame_ts, bbox_id),TTL frame_ts + INTERVAL 7 DAY自动清理
  • 查询层:创建Skipping index:
    ALTER TABLE detections ADD INDEX conf_idx(confidence) TYPE minmax GRANULARITY 3;
    对WHERE confidence > 0.8查询,conf_idx将part过滤率提升至92%
  • 边缘协同:在Orin上部署clickhouse-keeper替代ZooKeeper,减少网络依赖

效果:写入延迟稳定在15ms,SELECT * FROM detections WHERE camera_id=1 AND confidence>0.8P95=38ms,满足实时巡检需求。

5. 常见问题排查速查表:那些让你深夜加班的典型陷阱

问题现象根本原因快速诊断命令解决方案我踩过的坑
查询突然变慢,system.query_log显示read_rows暴涨WHERE条件未命中skipping index,或PREWHERE缺失SELECT * FROM system.query_log WHERE query_id='xxx' FORMAT Vertical添加PREWHERE,或重建skipping index:ALTER TABLE t ADD INDEX idx_foo(foo) TYPE minmax GRANULARITY 1曾为省事在String字段上建bloom_filter索引,结果写入吞吐下降60%,因Bloom Filter构建开销过大
INSERT写入缓慢,system.processes中大量INSERT状态max_insert_block_size过小,或replicated_deduplication_window溢出SELECT value FROM system.settings WHERE name='max_insert_block_size'调大max_insert_block_size至1000000,检查ZK中/clickhouse/tables/t/replicas/r/inserts节点数某次升级后max_insert_block_size被重置为1024,导致写入QPS从5000跌至800
OPTIMIZE TABLE FINAL执行数小时不结束parts数量过多,或min_bytes_for_wide_part设置不当导致Merge无法合并SELECT count(), sum(bytes_on_disk) FROM system.parts WHERE table='t'先执行DETACH PARTITION冷数据,再OPTIMIZE热数据;或临时调大background_pool_size为“彻底清理”执行OPTIMIZE TABLE FINAL,结果阻塞写入3小时,后来改用ALTER TABLE t FREEZE PARTITION备份后重建
GROUP BY查询内存溢出,日志报Memory limit exceededmax_bytes_before_external_group_by过小,或GROUP BY字段基数过高SELECT query, read_rows, memory_usage FROM system.query_log WHERE type='QueryFinish' ORDER BY memory_usage DESC LIMIT 5计算GROUP BY字段唯一值数量:SELECT uniqCombined(user_id) FROM t,若>1亿,改用GROUP BY+LIMIT分页曾对user_id直接GROUP BY,未意识到其基数达2亿,应先用arrayReduce('uniq', groupArray(user_id))采样估算
副本同步延迟,system.replicas中queue_size>1000网络抖动或ZK响应慢,或replicated_max_parallel_fetches不足SELECT * FROM system.replicas WHERE table='t' FORMAT Vertical增加replicated_max_parallel_fetches=16,检查ZKLatency_avgZK集群共用其他业务,Latency_avg常达200ms,后为ClickHouse独占ZK集群
DISTINCT查询极慢uniqCombined函数未启用max_bytes_before_external_distinctSELECT value FROM system.settings WHERE name='max_bytes_before_external_distinct'设为max_bytes_before_external_group_by的同值,或改用uniqHLL12近似去重为精确去重坚持用uniqExact,结果内存爆掉,后接受uniqHLL12误差<1.5%的业务妥协
JOIN查询超时,system.processes中JOIN状态挂起右表过大,或JOIN键未在ORDER BY中靠前EXPLAIN PIPELINE SELECT ... JOIN ...查看JoiningTransform输入行数改用GLOBAL IN,或对右表建Dictionary,或JOIN前用WHERE过滤右表某次JOIN用户画像表(10亿行),未加WHERE city='Beijing',导致全表广播

最后分享一个小技巧:所有ClickHouse优化,最终都要回归到system.parts这张表。我每天晨会第一件事就是运行:

SELECT table, count() as parts_count, round(avg(bytes_on_disk)/1024/1024, 2) as avg_mb_per_part, round(sum(rows)/count(), 0) as avg_rows_per_part, round(avg(creation_time), 0) as avg_age_hours FROM system.parts WHERE active=1 AND database='default' GROUP BY table HAVING parts_count > 100 OR avg_mb_per_part < 5 OR avg_age_hours > 720 ORDER BY parts_count DESC;

这个查询能一眼揪出所有潜在风险表——parts_count>100意味着Merge压力,avg_mb_per_part<5说明写入太碎,avg_age_hours>720(30天)表示数据老化需归档。它比任何监控图表都直接,因为ClickHouse的性能,就藏在每一个parts的命名和大小里。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/3 1:38:03

Primetime电压缩放实现:DVFS、MMMC与UPF流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/3 1:37:17

基于机器学习的网络舆情分析系统实战:从数据清洗到增量更新

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/3 1:35:43

.NET Framework窗体应用中YAML配置解析实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/3 1:35:20

嘉立创EDA“引脚与焊盘未对应”报错排查与解决指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/3 1:35:10

MFC迷宫游戏中的栈路径搜索与内存布局实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华