开始之前
nyc_taxi.trips_small_inferred 表。若要完全按照示例运行,如尚未创建并加载该表,请执行以下操作:
设置示例数据集
设置示例数据集
源 Parquet 文件约为 5.8 GB。加载该文件可能需要几分钟,具体取决于您的网络状况和可用资源。
选择优化方向
如果证据不属于上述任何类别,请回到查询计划,而不是强行套用某种优化方向。
减少数据读取量
- 适用场景: 查询读取了宽列或不需要的列。
- 更改: 减少查询读取的列大小或列数。
- 验证: 在相同条件下比较
read_bytes、内存使用量和耗时。
审查列类型
String;并选择能安全表示预期范围的最小有符号或无符号数值类型。对于时间列,请使用 Date 或 DateTime,除非需要 Date32 或 DateTime64 提供的更大范围或小数精度。
谨慎使用可空列
Nullable 列除存储值外,还会存储单独的空值掩码,ClickHouse 也必须读取和处理该掩码。仅当区分空值与该类型的默认值具有实际意义时,才应使用它。如果可以保证某列始终包含值,使用不可为空的类型可避免这部分额外开销。
在更改列之前,应检查源数据和摄取路径,不要假设当前观察到的非空数据会始终保持非空。优化实战示例 演示了如何识别包含空值的列,以及如何衡量更改 schema 的影响。
对重复值使用字典编码
LowCardinality 使用字典编码,对于状态值、国家/地区代码等字符串列,以及其他不同值数量远少于行数的维度,通常非常有效。约 10,000 个不同值可作为识别候选列的参考起点,而非固定限制。应避免将其用于标识符和其他大多唯一的列,并在更改类型前后比较测量结果。
有关更详细的指导,请参阅选择数据类型。
仅读取所需列
SELECT *,尤其是对于宽表,或查询仅返回每行少量字段时。
使用 system.query_log 中的 read_bytes,比较缩小所选列范围前后读取的数据量。如果 read_bytes 仍然较高,请检查查询计划,查看是否有仍需读取额外列的表达式、过滤器、联接或嵌套查询。
例如,如果仪表板只需要上车时间、支付类型和总金额,请选择这些列,而非完整行:
SELECT * 查询进行比较。返回的行数不变,但 read_bytes 应反映出读取的列更少。
让数据布局与查询相匹配
- **适用场景:**选择性过滤器仍会读取大量 parts 或粒度。
- **调整:**使物理布局与频繁执行的查询所使用的过滤器相匹配。
- **验证:**比较
EXPLAIN indexes = 1选中的 parts 和粒度,然后检查read_rows、read_bytes和耗时。
从排序键入手
MergeTree 家族表,排序键决定行在磁盘上的排列方式。默认情况下,它还作为定义稀疏主索引的主键。与 OLTP 数据库中的主键不同,ClickHouse 主键不保证唯一性。其性能优势在于,ClickHouse 可以跳过无法满足查询’过滤条件的粒度。
应优先选择经常用于高选择性过滤的列,并考虑它们在键中的顺序。将相关值聚集在一起也有助于提高压缩率。当查询的分组或排序顺序与该键一致时,ClickHouse 可能会对 GROUP BY 或 ORDER BY 使用按序优化。
在测试不同排序键的前后,比较 EXPLAIN indexes = 1 选中的 parts 和粒度。同时,在相同条件下比较 read_rows、read_bytes 和耗时。有关详细的选择建议,请参阅选择主键。
示例表使用 ORDER BY (),因此以下高选择性日期过滤器没有可用于跳过粒度的排序键:
在 ClickHouse 25.9 及更高版本中,这些设置可确保
EXPLAIN 显示所使用的索引,以及这些索引排除的 parts 和粒度。pickup_datetime 的表,然后对该表运行相同的 EXPLAIN。在使用耗时或内存指标评估整体变更前,计划的主键部分应显示选中的粒度更少。
评估其他索引和数据布局方案
EXPLAIN indexes = 1 确认查询确实会剪枝分区。
为局部过滤条件添加数据跳过索引
数据跳过索引会存储元数据,使 ClickHouse 能够避免读取不可能匹配过滤条件的块。当排序键无法支持某个重要过滤条件,且匹配值在块内足够集中时,它最为有用。
例如,当大多数块不包含要查找的值时,布隆过滤器索引可帮助进行等值查找。应先评估数据类型和排序键,再使用跳过索引。很少能排除块的索引会增加存储和评估开销,却无法显著减少工作量。使用具有代表性的数据测试索引类型和粒度,然后使用 EXPLAIN indexes = 1 比较选中的数据粒度,并检查 read_rows、read_bytes 和耗时。
选择性使用投影
投影会与表一同存储替代的数据布局。它们可以提供另一种排序键或预计算结果,ClickHouse 可以选择适用的投影,而无需在查询中直接引用它。
例如,按 payment_type 排序的投影可以支持基表排序无法支持的常用过滤条件。对于基表排序无法高效支持的重要访问模式,应仅使用少量投影。
投影会存储额外的索引或列数据,并增加插入和合并时的工作量;全列投影会复制其存储的列。大量使用投影还可能增加查询时选择最优投影所需的工作量。对于具有许多不同访问模式的大型部署,使用较少的投影或独立的专用表通常更易于运维。在这些机制之间进行选择时,请参阅materialized views 与投影。
为按支付类型和接载时间过滤的查询添加替代排序,同时仍查询源表:
EXPLAIN projections = 1 确认 ClickHouse 是否选择了该投影,以及是否读取了更少的行或字节。将此模式广泛应用前,还应评估插入和存储开销。
预计算重复性工作
- 适用场景: 相同的转换或聚合反复占据大部分查询时间。
- 变更: 将重复性计算移至摄取阶段、按计划刷新或专用数据布局中。
- 验证: 确认查询读取更小的结果集,并在查询时执行更少计算,同时摄取或刷新带来的工作量仍在可接受范围内。
每个部分均包含基础实现、主要运维权衡,以及验证结果的方法。
增量materialized view
pickup_date 分组并使用 sum(trip_count) 查询目标表,以便在查询时合并等待后台合并的行。该视图仅处理新插入的数据,因此需要单独回填现有源数据。将耗时和读取行数与原始聚合进行比较,以验证变更,然后确认额外的插入开销可以接受。
可刷新materialized view
system.view_refreshes,确认刷新耗时、状态和频率是否适合该工作负载。
专用表
Nullable 移除。请确认这种处理方式符合工作负载的数据要求。仪表板必须显式查询此表,摄取管道也必须确保其保持最新。请通过比较读取的行数和字节数、内存使用量及耗时与源表查询来验证此更改。决策时还应考虑额外的存储和管道维护成本。