别再踩坑!ClickHouse 新手最常犯的 13 个错误(附权威避坑指南)

张开发
2026/5/5 6:49:44 15 分钟阅读
别再踩坑!ClickHouse 新手最常犯的 13 个错误(附权威避坑指南)
本文字数3854估计阅读时间10 分钟作者Dale McDiarmid, Tom Schreiber and Geoff Genz本文在公众号【ClickHouseInc】首发在 ClickHouse我们始终在打磨和优化入门体验。ClickHouse 是一款复杂且功能强大的软件它引入了许多对用户而言较为陌生的概念因此在使用初期很容易出现各种错误。本文将总结我们在新用户中最常见的 13 个问题分析它们产生的原因并给出正确的处理方式。对于在大规模场景下管理 ClickHouse 时遇到挑战的用户ClickHouse Cloud 可以自动处理许多常见的入门问题以及后续的扩展难题。1. 过多的数据分片 (parts)一个常见的 ClickHouse 报错如下DB::Exception: Too many parts这个错误通常发生在数据插入期间会出现在 ClickHouse 日志中或者作为对 INSERT 请求的返回结果。要理解这一错误首先需要理解 ClickHouse 中数据分片 (part) 的概念。在 ClickHouse 中一张表由多个数据分片 (part) 组成这些分片中的数据会按照用户指定的主键进行排序默认情况下为建表时的 ORDER BY 子句详见 Index Design。当向表中插入数据时每一次插入都会生成独立的数据分片并且每个分片内部都会按照主键进行字典序排序。例如如果主键为 (CounterID, Date)则分片中的数据会先按 CounterID 排序再在每个 CounterID 内按 Date 排序。在后台ClickHouse 会持续对这些分片进行合并以实现更高效的存储这一机制类似于 Log-structured merge tree。每个分片都维护自己的主索引以便在查询时能够高效定位数据所在位置。当分片被合并时它们的主索引也会一并合并。随着分片数量不断增加查询性能必然会下降因为系统需要检查更多索引并读取更多文件。在分片数量过高的情况下用户还可能遇到启动时间变慢的问题。分片过多会带来更多后台合并操作同时增加系统维持低分片数量和高查询性能的压力。虽然合并过程是并发执行的但在使用不当或配置不合理的情况下分片数量可能会超过内部可配置的限制parts_to_throw_insert, max_parts_in_total。这些限制虽然可以调整但通常会以牺牲查询性能为代价。更重要的是一旦需要调高这些限制往往意味着当前的使用模式存在问题。除了影响查询性能之外在复制架构中过多的分片还会给 ClickHouse Keeper 带来额外压力。那么为什么会产生如此多的分片呢分区键选择不当一个常见原因是选择了基数过高的分区键。在创建表时用户可以选择指定某一列作为分区键数据将按照该键进行分区。每一个分区键值都会对应一个新的文件系统目录。分区通常用于数据管理使用户能够在逻辑上对表中的数据进行清晰划分。例如通过 DROP PARTITION 可以快速删除某个分区的数据子集。然而这一强大的功能也很容易被误用有些用户会将其误认为是一种简单的查询优化手段。需要特别注意的是不同分区之间的分片永远不会进行合并。如果选择了一个高基数的列例如 date_time_ms作为分区键那么分片将分散在成千上万个目录中彼此之间无法成为合并候选从而超过预设限制并在后续 INSERT 时触发 “Too many inactive parts (N). Parts cleaning are processing significantly slower than inserts” 错误。解决方法很简单选择基数小于 1000 的分区键。大量小批量插入除了分区键选择不当之外频繁的小批量插入也会导致这一问题。每一次向 ClickHouse 执行 INSERT都会将当前插入块转换为一个新的分片。为了将分片数量控制在合理范围内建议在客户端进行数据缓冲并以批量方式插入数据——每次至少 1,000 行而 10,000 到 100,000 行的批量大小通常更为理想。如果无法在客户端实现缓冲也可以使用 async inserts 将这一工作交由 ClickHouse 处理。在这种模式下ClickHouse 会先在内存中缓冲多次插入请求然后将其作为一个批量分片统一刷新到底层表中。当达到以下任一可配置阈值时就会触发刷新缓冲区大小限制async_insert_max_data_size默认 1MB、时间阈值async_insert_busy_timeout_ms默认 1 秒或排队查询数量上限async_insert_max_query_number默认 100。由于数据在刷新之前会暂存在内存中因此务必保持 wait_for_async_insert1默认值确保客户端只有在数据安全写入磁盘之后才收到确认从而避免在刷新前服务器异常崩溃时出现无提示的数据丢失。Buffer 表是一种历史遗留的替代方案。它的一个独特优势是在数据刷新到目标表之前缓冲区中的数据就已经可以被查询。然而Buffer 表存在明显缺陷不支持复制机制与 FINAL 或 SAMPLE 不兼容并且在服务器异常重启时可能发生数据丢失。在使用 SharedMergeTree 的 ClickHouse Cloud 环境中每个节点都会维护独立的缓冲状态这进一步增加了系统复杂性。对于绝大多数场景推荐使用 async inserts。只有在确实需要在刷新前查询缓冲数据这一硬性需求下才应考虑使用 Buffer 表。过多的物化视图该错误的另一种常见原因是创建了过多的物化视图。物化视图本质上类似于一种触发器当数据块被插入到源表时它会自动执行。物化视图会对数据进行转换例如通过 GROUP BY 进行聚合然后将结果写入到另一张表中。这种方式通常用于在 INSERT 阶段预先计算聚合结果从而加速后续查询。用户可以根据需要创建多个物化视图但这也可能导致产生大量分片parts。因此我们通常建议在创建物化视图时充分评估其成本并在可能的情况下进行合并与精简。以上列举的情况并非该错误的全部原因。例如mutations如下文所述同样会带来合并压力并导致分片数量累积。需要指出的是“Too many parts” 虽然是最常见的报错之一但它只是上述错误配置的一种表现形式。分区键设计不当还可能引发其他问题包括但不限于“文件系统 inode 不足”、备份耗时过长以及复制延迟同时导致 ClickHouse Keeper 负载升高。2. 过早进行横向扩展我们经常遇到新的自托管用户询问关于编排方案以及如何扩展到数十甚至上百节点的建议。尽管 Kubernetes 等技术让部署多个无状态应用实例变得相对简单但对于 ClickHouse 而言在绝大多数情况下并不需要这样做。与某些数据库受限于固有瓶颈例如 JVM 堆大小不同ClickHouse 从架构设计之初就能够充分利用单机的全部资源。我们常见的成功案例中ClickHouse 部署在拥有数百 CPU 核心、数 TB 内存以及数 PB 存储空间的服务器上。大多数分析型查询都包含排序、过滤和聚合阶段这些阶段都可以独立并行执行并且默认会使用与 CPU 核心数相同的线程数从而在单次查询中充分利用整台机器的资源。优先进行纵向扩展有诸多优势最显著的是成本更低、运维复杂度更小以及由于减少了诸如 JOIN 等操作中的网络数据传输而带来的更佳查询性能。当然生产环境中仍然需要基础的冗余配置但对于除超大规模场景外的绝大多数用例来说两台机器通常已经足够。ClickHouse Cloud 同时支持纵向扩展提升副本规格和横向扩展增加副本数量。得益于其计算与存储分离架构SharedMergeTree这两种扩展方式都可以平滑进行。有关扩展策略的更多细节请参考 Cloud 扩展文档。我们强烈建议在考虑横向扩展之前优先评估纵向扩展。简而言之先纵向后横向。3. Mutation 带来的挑战在 OLAP 场景中修改数据的需求并不常见但有时确实无法避免。ClickHouse 在处理不可变数据时性能最佳因此任何需要在插入之后再更新数据的设计模式都应谨慎评估。不过ClickHouse 仍然提供了两种就地修改数据的机制通过 ALTER TABLE ... UPDATE 实现的经典 mutations —— 会重写整个受影响的数据分片parts适用于不频繁的大批量修改。通过基于 patch parts 的 UPDATE 实现的轻量级更新 —— 仅写入发生变更的列值以紧凑的增量分片形式保存更适合频繁或定向更新。经典 mutations经典 mutations 的工作方式是重写包含受影响列的整个数据分片。该过程与后台合并共享同一个线程池。在自托管的复制环境中每个副本都需要独立执行 mutation。因此这类操作会消耗大量 CPU 和 IO 资源应谨慎安排并通常仅限管理员执行。mutation 带来的资源压力可能以多种形式表现出来。最常见的是正常的后台合并被积压进而再次触发前文提到的 “too many parts” 问题。此外还可能出现复制延迟。管理员可以通过 system.mutations 表查看当前排队或执行中的 mutation。需要注意的是可以通过 KILL MUTATION 取消 mutation但无法回滚已经执行的更改。基于 patch parts 的轻量级更新轻量级更新采用完全不同的实现方式。它不会重写整个数据分片而是生成一个小型且紧凑的 “patch part”其中只包含变更后的列值以及用于定位受影响行的元数据。这些 patch 在读取时会动态应用因此修改可以立即生效并在常规后台合并过程中高效物化——利用 ClickHouse 原本就要执行的合并操作。在许多工作负载下这种方式的性能可能比经典 mutations 快高达 1,000 倍。相同机制也可用于删除操作通过 patch part 设置 _row_exists 0 标记而无需重写整列数据。关于 patch parts 的详细原理可参考我们关于 ClickHouse 快速 UPDATE 的系列文章Part 1 — purpose-built engines以及 Part 2 — SQL-style updates。ClickHouse Cloud 中的 mutations在 ClickHouse Cloud 中表使用 SharedMergeTree 引擎。数据存储在共享对象存储中元数据通过 ClickHouse Keeper 协调管理。这种架构改变了 mutation 的执行方式由于副本之间无需直接通信且数据统一存放在共享存储中因此 mutation 不再需要在每个副本上分别执行。重写后的分片会写入共享存储并通过元数据更新对所有副本可见。这不仅加快了 mutation 的执行速度也避免了自托管集群在高强度 mutation 负载下可能出现的复制延迟。在 ClickHouse Cloud 中经典 mutations 和基于 patch parts 的轻量级更新均可使用。去重我们经常看到用户因为存在重复数据而手动安排合并。通常建议在上游系统中解决重复问题在数据写入 ClickHouse 之前完成去重。如果无法做到也可以采用以下方式在查询阶段去重或者使用 ReplacingMergeTree 引擎。在查询阶段去重可以通过对唯一标识一行数据的字段进行分组并结合带有时间字段的 argMax 函数来获取其他列的最新值。ReplacingMergeTree 会在合并过程中对具有相同排序键ORDER BY 键的记录进行去重。但需要注意这种去重方式属于“尽力而为”因为后台合并是在非确定性时间间隔内执行的某些分片可能暂时不会被合并因此无法保证绝对无重复。如果需要在查询时强制去重可以使用 FINAL 修饰符。虽然 FINAL 会增加一定开销但在最近的版本中已获得显著性能优化包括多线程处理能够满足许多生产环境需求。当需要从 ClickHouse 中删除数据例如出于合规或去重目的时也可以使用轻量级删除而非 mutation。轻量级删除通过带 WHERE 条件的 DELETE 语句实现仅将符合条件的行标记为已删除。这些标记会在查询时生效并在后续分片合并时被清理。轻量级删除已达到生产可用级别在大多数场景下都比使用 mutation 更高效除非是执行大规模批量删除操作。需要注意的是轻量级删除目前不支持带有 projections 的表。4. 处理复杂与半结构化数据除了常见的基础数据类型外ClickHouse 还原生支持多种复杂类型例如 Nested、Tuple、Map 和 JSON。总体原则很简单如果数据结构是已知且稳定的显式定义列始终能够获得最佳的压缩率、写入性能和查询速度。但当数据本身具有半结构化特性或者 Schema 持续演进时ClickHouse 提供的原生 JSON 类型则是更合适的选择。面向半结构化数据的 JSON 类型ClickHouse 提供了专为优化 JSON 数据存储与处理而设计的原生 JSON 数据类型。在内部实现上ClickHouse 会将 JSON 路径拆分并存储为专用子列 —— 相当于在后台自动将 JSON 扁平化为真实列同时保留完整的压缩能力和向量化执行性能。默认情况下最多会将 1,024 个唯一路径存储为独立的子列。超过该数量的路径则会存储在一种高效的共享数据结构中。JSON 子列同样可以用于排序键表达式以及数据跳过索引从而享有与普通列相同的查询优化能力。需要强调的是JSON 类型并不是 Schema 设计的替代方案。对于结构已知且稳定的字段显式列定义始终能够提供最佳性能。合理的做法是对结构明确的数据部分使用显式列而对真正动态变化的部分使用 JSON 类型。5. 过度使用 Nullable我们经常看到用户大量使用 Nullable 类型它允许列存储 Null 值。从底层实现来看这会额外生成一个 UInt8 类型的辅助列。每当查询或处理 Nullable 列时这个额外列都需要参与计算。这不仅会增加存储空间开销而且几乎总会对查询性能产生负面影响。在大多数场景下我们建议尽量避免使用 Nullable 列。更推荐的做法是使用一个能够表示“空值”的默认值例如在 String 列中使用空字符串。6. 在插入阶段去重许多初次接触 ClickHouse 的用户都会对其去重机制感到意外。这通常发生在重复插入相同数据却似乎“没有生效”的情况下。例如考虑以下示例CREATE TABLE temp ( timestamp DateTime, value UInt64 ) ENGINE MergeTree ORDER BY tuple() INSERT INTO temp VALUES (2022-10-21, 10), (2022-10-22, 20), (2022-10-23, 15), (2022-10-24, 18) INSERT INTO temp VALUES (2022-10-21, 10), (2022-10-22, 20), (2022-10-23, 15), (2022-10-24, 18) clickhouse-cloud :) SELECT * FROM temp SELECT * FROM temp ┌───────────timestamp─┬─value─┐ │ 2022-10-21 00:00:00 │ 10 │ │ 2022-10-22 00:00:00 │ 20 │ │ 2022-10-23 00:00:00 │ 15 │ │ 2022-10-24 00:00:00 │ 18 │ └─────────────────────┴───────┘请注意在上述示例中共插入了 8 行数据但 SELECT 查询结果仅显示 4 行。这种行为通常会让新用户感到困惑。其根本原因在于 replicated_deduplication_window 这一设置。当数据写入 ClickHouse 时会生成一个或多个数据块parts。在复制环境中例如 ClickHouse Cloud系统还会在 ClickHouse Keeper 中记录这些数据块的哈希值。之后插入的新数据块会与已有哈希进行比对如果检测到完全一致的块则该插入会被自动忽略。这种机制的优势在于当客户端因网络中断等原因未收到写入确认时可以安全地重试插入操作而无需担心数据被重复写入。需要注意的是这种去重仅在数据块完全一致时才会生效——包括块大小相同、行内容相同且顺序一致。默认情况下系统仅保存最近 100 个数据块的哈希值该数值可调整。但如果将该值设置得过高会因为需要进行更多哈希比较而降低插入性能。对于非复制环境也可以通过设置 non_replicated_deduplication_window 启用相同的去重行为。在这种情况下哈希值会存储在本地磁盘上。7. 主键选择不当许多刚接触 ClickHouse 的用户对其主键机制理解不够深入。与基于 B()-Tree 的 OLTP 数据库不同后者主要针对快速定位单行数据进行优化ClickHouse 使用的是稀疏索引结构专为每秒百万级写入和 PB 级数据规模设计。不同于 OLTP 数据库通过索引直接定位单行ClickHouse 的索引依赖磁盘数据的有序存储用于快速识别可能满足查询条件的数据块——这正是分析型查询的典型需求。换句话说在数据被送入执行引擎之前索引会先快速定位 part 文件中可能匹配的区域。关于磁盘数据布局的详细说明强烈建议阅读相关指南。这种机制在查询性能和数据压缩方面的效果很大程度上取决于用户在建表时通过 ORDER BY 子句选择的主键列。通常应优先选择那些经常出现在过滤条件中的列而且主键列数量一般不应超过 2 到 3 个。列的顺序同样至关重要它不仅会影响压缩效果还会影响对非首列字段的过滤效率。为了同时优化查询中过滤次级键列的效率以及列文件的压缩率通常建议按照列基数从低到高的顺序排列主键列。完整的原理分析可参考相关文档。8. 过度使用数据跳过索引在需要优化查询时主键通常是首选工具。然而一张表只能定义一个主键而实际查询模式往往多种多样不可避免会出现无法高效利用主键的情况。在这些场景下ClickHouse 可能需要在应用 WHERE 条件时扫描整张表的数据。多数情况下这依然足够快但部分用户会进一步引入数据跳过索引希望借此显著提升查询性能。数据跳过索引会增加额外的数据结构使 ClickHouse 能够在确定某些数据块不满足 WHERE 条件时直接跳过读取。更具体地说它们会在数据块粒度即 marks上建立索引从而在条件不匹配时跳过对应的粒度范围。在特定场景下数据跳过索引确实可以加速查询。但在实践中它们往往被滥用设计难度较高且效果并不直观。我们经常看到它们反而增加了表结构的复杂度降低了写入性能却很少真正改善查询效率。因此我们始终建议在使用前充分理解其原理与最佳实践。在大多数情况下只有在尝试并评估过其他优化手段之后才应考虑数据跳过索引。例如可以优先考虑优化主键设计、使用 projections 或物化视图。通常只有当主键与目标的非主键列或表达式之间存在较强相关性时跳过索引才有意义。如果缺乏这种相关性索引往往会对大多数数据块都产生匹配导致所有粒度仍需读入并评估。此时不仅没有收益反而增加了额外开销使全表扫描变得更慢。9. LIMIT 并不总能提前结束查询 点查询我们经常看到来自 OLTP 背景的用户使用 LIMIT 子句来“优化”查询希望通过减少返回行数来提升性能。从 OLTP 的角度来看这似乎合乎逻辑返回更少数据查询就更快——对吗答案是视情况而定。LIMIT 是否能够带来性能收益取决于查询能否以流式方式执行。例如SELECT * FROM table LIMIT 10 这样的查询只需扫描前几个 part 的少量粒度在获取 10 行结果后即可返回。如果按主键字段排序且 optimize_read_in_order 默认为 1同样可以实现提前结束。但如果执行 SELECT a FROM table ORDER BY b LIMIT N而表是按 a 排序而非按 b 排序那么 ClickHouse 无法避免扫描整张表也就无法提前终止查询。对于聚合查询情况更加复杂。除非查询按主键分组并将 optimize_aggregation_in_order 设置为 1否则通常仍需扫描整张表。在满足条件时一旦获得足够的结果系统会发出终止信号。如果查询的前置步骤例如过滤支持流式处理那么查询可以提前结束。但在多数情况下聚合操作必须先读取并处理全部数据最后再应用 LIMIT。举例来说我们加载 UK Property Price Paid 教程中的数据表该表包含 2755 万行数据可在 play.clickhouse.com 环境中体验。当 optimize_aggregation_in_order0 时即使是按主键分组的聚合查询也会在应用 LIMIT 1 之前完成全表扫描clickhouse-cloud :) SELECT postcode1, postcode2, formatReadableQuantity(avg(price)) AS avg_price FROM uk_price_paid GROUP BY postcode1, postcode2 LIMIT 1; ┌─postcode1─┬─postcode2─┬─avg_price───────┐ │ AL4 │ 0DE │ 335.39 thousand │ └───────────┴───────────┴─────────────────┘ Elapsed: 3.028 sec, read 27.55 million rows, 209.01 MB.当 optimize_aggregation_in_order1 时查询则可以提前结束从而处理更少的数据clickhouse-cloud :) SELECT postcode1, postcode2, formatReadableQuantity(avg(price)) AS avg_price FROM uk_price_paid GROUP BY postcode1, postcode2 LIMIT 1 SETTINGS optimize_aggregation_in_order 1; ┌─postcode1─┬─postcode2─┬─avg_price───────┐ │ AL4 │ 0DE │ 335.39 thousand │ └───────────┴───────────┴─────────────────┘ Elapsed: 0.999 sec, read 4.81 million rows, 36.48 MB.此外即使是有经验的用户也常在多节点分片环境中忽视 LIMIT 的实际行为。分片允许将数据拆分或复制到多个 ClickHouse 实例。当带有 LIMIT N 的查询发送到分片表例如通过分布式表时该 LIMIT 会被下推到每个分片。每个分片都需要各自计算前 N 条结果再汇总到协调节点。当查询本身需要全表扫描时这种模式尤其消耗资源。典型场景是点查询point lookup即仅希望获取少量特定行数据。虽然通过合理的索引设计可以实现高效点查询但如果设计不当即使加上 LIMIT也可能导致极高的资源消耗。10. 表意外变为只读在自托管的复制环境中如果某个节点与协调服务之间的连接中断相关表可能会意外变为只读状态。一旦发生这种情况该节点将无法继续参与复制流程并且在连接恢复之前拒绝所有写入请求。最常见的原因是协调服务资源不足——例如在生产环境中将其与 ClickHouse 部署在同一台服务器上或为其分配了过少的 CPU 与内存资源。通常的解决方式是将协调服务部署在资源充足的专用机器上。对于自托管部署推荐使用 ClickHouse Keeper 作为协调服务。它专为 ClickHouse 设计采用 C 编写无需 JVM 调优并且在元数据处理模式上比 ZooKeeper 与 ClickHouse 更加契合。如果当前仍在使用 ZooKeeper建议迁移至 ClickHouse Keeper。在 ClickHouse Cloud 中不存在该问题因为协调服务由平台统一托管和管理。11. 查询内存超限对于新用户来说ClickHouse 往往像“魔法”一样——即使面对超大规模数据集或复杂查询也能保持极高性能。但在真实生产环境中查询复杂度最终可能触及系统资源上限。查询内存超限通常由多种原因引起其中最常见的是在高基数字段上执行大规模 JOIN 或聚合操作。如果这些查询对业务至关重要且对性能有严格要求最直接的方式通常是提升实例规格——在 ClickHouse Cloud 中这一过程可以自动完成以确保查询始终保持良好响应。在自托管环境下扩容往往更复杂但仍有多种优化手段可供选择。聚合对于内存消耗较大的聚合或排序场景可以分别使用 max_bytes_before_external_group_by 和 max_bytes_before_external_sort 两个设置。前者允许在聚合内存超过阈值时将中间结果写入磁盘即 external group by。虽然这会降低查询性能但可以避免因内存不足导致查询失败。后者则针对排序操作当排序数据超出可用内存时同样可以溢写到磁盘。在分布式环境中这一点尤为重要。例如当协调节点接收来自各个分片的已排序结果时可能需要对超出自身内存容量的数据集进行再次排序。通过设置 max_bytes_before_external_sort可以允许排序过程使用磁盘作为补充空间。此外当查询中存在 GROUP BY 后再执行 ORDER BY 并带 LIMIT 的情况尤其是在分布式查询中该设置也非常有帮助。JOINClickHouse 支持所有标准 SQL JOIN 类型以及 ANY、ASOF、SEMI 和 ANTI 等专用变体这些变体在常见分析场景中可以显著提升性能。但需要注意的是JOIN 操作本身通常是高内存消耗的理解不同 JOIN 策略之间的权衡对于避免内存问题至关重要。在 ClickHouse 中实现高效 JOIN 的通用原则包括选择适合数据特征的 JOIN 算法。通过 join_algorithm 设置可以在多种算法之间切换以在内存使用和执行性能之间取得平衡。Hash join 性能高但依赖充足内存。Grace hash 会将数据划分为多个桶并在内存耗尽时写入磁盘。Sort-mergepartial_merge、full_sorting_merge适用于已排序数据或当两侧数据规模过大无法全部载入内存时。Direct 算法在右表基于字典或小型内存表时可实现高效键值查找。将 join_algorithm 设置为 auto可以让 ClickHouse 根据运行时资源状况自动选择最优算法。使用专用 JOIN 类型。ANY JOIN 仅返回右表的首条匹配记录非常适合用于维表关联或数据补充场景可显著降低内存占用。ASOF JOIN 专为时间序列设计用于查找最接近的匹配记录而非精确匹配。尽早过滤。在可能的情况下在执行 JOIN 之前应用 WHERE 条件以减少参与 JOIN 的数据量。将较小的表放在右侧。对于默认的 Hash JOINClickHouse 会基于右表构建内存哈希表。因此将较小表置于右侧可以有效降低内存消耗。尽管查询优化器在某些情况下可以自动调整 JOIN 顺序但理解这一原则依然十分重要。异常或不受控查询内存问题的另一常见来源是缺乏限制的用户查询。在没有设置配额或查询复杂度限制的情况下用户可能提交高消耗的“异常查询”。如果 ClickHouse 面向广泛且多样化的用户群体开放这些控制机制对于保障服务稳定性至关重要。我们在 play.clickhouse.com 环境中就通过配额与限制机制确保系统在共享场景下依然稳定。ClickHouse 还支持 Memory overcommit内存超额分配机制。过去查询会受到 max_memory_usage默认 10GB这一硬限制的约束。这种方式较为粗放虽然可以通过提高阈值满足单个查询需求但可能会影响其他用户。启用 Memory overcommit 后在系统资源允许的情况下可以运行更高内存消耗的查询。当服务器达到最大内存限制时ClickHouse 会识别哪些查询的内存超额程度最高并尝试终止其中一个查询。被终止的未必是触发限制的那个查询。如果当前查询未被终止它会短暂等待以便高内存查询被清理后继续执行。这种机制确保低内存查询始终能够运行而高资源消耗查询则会在服务器空闲时获得执行机会。相关行为可以在服务器级别或用户级别进行细粒度配置。12. 物化视图相关问题物化视图是 ClickHouse 最强大的功能之一。它允许在数据写入阶段预先完成数据转换与聚合将计算负担从查询阶段前移到写入阶段。这种机制常用于优化特定查询模式、在不同主键下重组数据或为下游汇总表提供预聚合结果。ClickHouse 支持两种物化视图类型。增量连续物化视图本质上充当插入触发器当数据写入源表时视图中的 SELECT 查询会针对新插入的数据块执行并将结果写入目标表。整个过程无需手动调度能够实现接近实时的数据转换。可刷新物化视图则采用不同机制会按照预定周期例如 REFRESH EVERY 1 HOUR重新构建完整结果集。它支持通过 DEPENDS ON 形成视图链可执行复杂的多表查询同时不会为每次插入增加额外开销。当需要实时结果时应选择增量视图当可以接受周期性更新且希望避免写入阶段额外负担时应选择可刷新视图。理解触发模型一个常见误区是认为增量物化视图能够感知整个源表的数据状态。实际上并非如此。增量物化视图仅在发生新的插入时触发并且只对新写入的数据块执行计算。它无法感知源表上的合并操作、分区删除或 mutations。因此如果通过 mutations 或分区操作修改源表数据物化视图的目标表不会自动同步这些变化。系统不会进行任何自动回溯更新。用户需要自行管理这一过程例如重建物化视图的目标表或在允许全表重算的场景下改用可刷新物化视图。单表上创建过多视图物化视图并非零成本。每一个附加在表上的增量视图在每次插入时都必须执行一次 SELECT并在目标表中生成新的分片part。如果在同一张表上附加过多视图——通常超过 50 个就已明显过多——将显著拖慢写入性能。这不仅来自每个视图带来的计算开销也来自多个目标表产生的分片压力最终可能触发前文提到的 “Too Many Parts” 问题。在可能的情况下应合并执行相似转换逻辑的视图。同时可以评估 parallel_view_processing 设置通过并发执行多个视图而非顺序执行以减轻写入延迟。高 CPU 消耗的状态函数状态函数state functions允许通过 Aggregate 函数在写入阶段进行增量聚合是非常强大的能力。但如果物化视图中计算大量状态函数尤其是 quantile 状态可能会显著增加 CPU 消耗从而降低写入速度。因此应审慎选择需要预计算的聚合逻辑并确保查询阶段节省的计算成本能够抵消写入阶段增加的开销。视图与目标表 Schema 不匹配物化视图常见的错误之一是其输出列与目标 AggregatingMergeTree 或 SummingMergeTree 表结构不匹配。目标表的 ORDER BY 子句必须与物化视图 SELECT 语句中的 GROUP BY 保持一致。此外视图 SELECT 输出的列名必须与目标表列名完全一致——不要依赖列顺序匹配。应通过别名确保列名对齐。目标表可以定义默认值因此视图输出列可以只是目标表列的子集。下面给出了正确示例CREATE MATERIALIZED VIEW test.basic ENGINE AggregatingMergeTree() ORDER BY (CounterID, StartDate) AS SELECT CounterID, StartDate, sumState(Sign) AS Visits, uniqState(UserID) AS Users FROM test.visits GROUP BY CounterID, StartDate;CREATE MATERIALIZED VIEW test.summing_basic ENGINE SummingMergeTree ORDER BY (CounterID, StartDate) AS SELECT CounterID, StartDate, count() AS cnt FROM source GROUP BY CounterID, StartDate;请注意在示例中需要将 count() 使用别名 counter以匹配目标表中的列名。CREATE MATERIALIZED VIEW test.mv1 (timestamp Date, id Int64, counter Int64) ENGINE SummingMergeTree ORDER BY (timestamp, id) AS SELECT timestamp, id, count() as counter FROM source GROUP BY timestamp, id;13. 在生产环境中使用实验性功能ClickHouse 会持续发布新功能。其中部分功能会被标记为 “experimental” 或 “beta”。Beta 功能由 ClickHouse 团队正式支持正处于逐步迈向生产可用阶段。Experimental 功能则通常是由团队或社区推动的早期原型尚未提供正式支持。这些功能最终要么成熟为生产级特性要么在发现并不适用于通用场景或存在更优实现方式时被弃用。我们鼓励用户在测试环境中尝试 beta 与 experimental 功能但不建议在生产系统中依赖 experimental 功能更不应围绕其构建核心业务逻辑。两类功能均需通过显式设置启用例如SET allow_experimental_variant_type 1在 ClickHouse Cloud 中如需启用 experimental 功能则需要通过支持渠道申请。结论如果你读到这里相信已经对在生产环境中运行 ClickHouse 集群有了较为全面的认识至少可以避开许多常见陷阱。尽管如此即便是经验丰富的运维人员在管理 PB 级数据规模的 ClickHouse 集群时也会面临挑战。如果希望在降低运维复杂度的同时继续享受 ClickHouse 的高性能与强大能力可以考虑使用 ClickHouse Cloud。征稿启示面向社区长期正文文章内容包括但不限于关于 ClickHouse 的技术研究、项目实践和创新做法等。建议行文风格干货输出图文并茂。质量合格的文章将会发布在本公众号优秀者也有机会推荐到 ClickHouse 官网。请将文章稿件的 WORD 版本发邮件至Tracy.Wangclickhouse.com

更多文章