开始之前
nyc_taxi.trips_small_inferred 表。如果尚未创建并加载该表,请按以下步骤操作:
设置示例数据集
设置示例数据集
源 Parquet 文件约为 5.8 GB。加载该文件可能需要几分钟,具体取决于您的网络状况和可用资源。
流程概述
- 针对推断的 schema 运行三个独立的工作负载查询,以建立基线。
- 创建一个列类型更精确的表,加载相同的数据,然后重新运行这些查询。
- 创建另一个使用相同优化 schema 和排序键的表,然后再次运行这些查询。
定义基准工作负载
这些设置有助于确保测试期间的多次运行结果可比。完成测量后,请将其恢复为原先的值。
system.query_log 获取这些值) ,请参阅建立可重复的基准。
按计算出的行程速度过滤
汇总日期范围内的行程
按乘客人数筛选
这三个查询均读取了约 3.29 亿行,接近表中的总行数。这说明可从两个方面优化该工作负载:先降低处理所选列的开销,再在过滤条件允许时减少所选行数。
优化 schema
避免使用不必要的 Nullable 列
Nullable 列除了存储值外,还会存储空值掩码。当需要区分空值与该类型的默认值时,应保留 Nullable;但对于确保始终包含值的列,应避免使用它。
统计示例 schema 中所用列的空值数量:
ratecode_id、mta_tax 和 payment_type 包含 NULL 值。优化后的 schema 保留了这些列的 Nullable,并将其从其他列中移除。
对重复值使用 LowCardinality
LowCardinality 使用字典编码,可减少含有大量重复值的列的存储和处理开销。应用前,请先检查不同值的数量:
LowCardinality 的候选列,但仍应针对实际工作负载评估其效果。约 10,000 个不同值可作为识别候选列的参考起点,而非固定上限。
选择更精确的数据类型
Int64 或 Float64 类型之前,先检查数值列的最小值和最大值:
UInt8,尽管 passenger_count 的最大值为 255。该示例还为 trip_distance 使用 Float32,并为货币值使用 Decimal32。此数据集中的所有值均在目标范围内;由于该工作负载比较的是聚合结果,因此示例接受较低的浮点精度和精确到分的货币精度。若需要保留精确的源值,请使用更宽的源数据类型。由于示例查询不需要秒以下精度,因此示例将推断出的 DateTime64 列替换为相同时区 UTC 中的 DateTime。
这些选择仅适用于此数据集。在应用相同更改前,请确认生产数据对范围、精度和可空性的要求。
应用 schema 变更
nyc_taxi.trips_small_inferred 替换为 nyc_taxi.trips_small_no_pk,然后重新运行这三个查询。原始示例记录了以下具有代表性的结果:
查询读取的行数仍然相同,但优化后的 schema 减少了这些行所表示的数据量。因此,无需更改数据筛选条件,即可缩短查询耗时并降低峰值内存占用。
比较两个表的磁盘占用空间:
优化排序键
MergeTree 家族中,排序键决定行在磁盘上的排列顺序。ClickHouse 会根据该顺序构建稀疏主索引,以跳过无法满足查询过滤条件的粒度。与许多事务型数据库中的主键不同,排序键不强制唯一性。
排序键应反映重要且经常执行的查询所使用的过滤条件。列的顺序很重要:当查询按键的有效前缀进行过滤时,键最为有效。若经常用于过滤,低基数列有时适合作为键的前导列;对于基于时间的工作负载,时间组件通常也很有用。有关详细的选择建议,请参阅选择主键。
本示例使用 (passenger_count, pickup_datetime, dropoff_datetime)。passenger_count 的不同值较少,且用于按乘客数量过滤;pickup_datetime 则用于按日期范围聚合。尽管 pickup_datetime 不是第一列,但在前导列未受约束时,ClickHouse 仍可利用后续键列的值排除数据。通常,按排序键的有效前缀进行过滤可实现更高效的裁剪。
应用排序键更改
nyc_taxi.trips_small_pk,然后重新运行这三个查询。
比较结果
schema 优化可减少存储占用,并降低处理所选值的开销。对于日期范围聚合,排序键带来的额外提升最为显著,因为 ClickHouse 可以跳过日期范围外的粒度。乘客数量过滤器读取的行数也更少,因为它按首个键列进行过滤。计算速度过滤器仍会读取整个表,因为其过滤条件基于
pickup_datetime、dropoff_datetime 和 trip_distance,而不是排序键的有效前缀。
使用 EXPLAIN indexes = 1 查看日期范围聚合:
在 ClickHouse 25.9 及更高版本中,这些设置可确保
EXPLAIN 显示所使用的索引,以及这些索引排除的 parts 和粒度。将该方法应用于您的工作负载
- 记录基线耗时、读取的行数和字节数,以及峰值内存占用。
- 检查所选列是否使用了不必要的宽类型或过于宽松的类型。
- 在不改变数据布局的前提下,应用 schema 变更并测量其影响。
- 根据重要的高频查询所使用的过滤器测试排序键。
- 使用
EXPLAIN indexes = 1比较读取的数据,然后在可比条件下重新运行基线查询。