何时使用字典而不是 JOINs
WHERE 过滤器筛掉。虽然较新版本 (24.12+) 在很多情况下会先下推过滤器再执行 JOIN,但这并不总能消除这部分开销。使用字典时,你可以内联调用 dictGet,因此只会对已经通过过滤的行执行查找。
不过,dictGet 并不总是最佳选择。如果你需要对表中很大比例的行调用 dictGet——例如,在 WHERE 条件中使用 dictGet('dict', 'elevation', id) > 1800——那么使用带原生索引的普通列可能更合适。对于普通列,ClickHouse 可以使用 PREWHERE 跳过部分粒度;而 dictGet 只能逐行求值,不支持索引。
经验法则如下:
- 当查找键已经可用时,使用字典来替代针对小型维度表的 JOIN。
- 当需要基于查找到的值对大量行进行过滤时,使用普通列和索引。
选择布局
LAYOUT 子句控制字典的内部数据结构。所有可用布局均记录在布局参考中。
选择布局时,请遵循以下准则:
flat— 速度最快的布局 (简单的数组偏移查找) ,但键必须为UInt64,并且默认上限为 500,000 (max_array_size) 。最适合小型到中型表中单调递增的整数键。稀疏的键分布 (例如键值分别为 1 和 500,000) 会浪费内存,因为数组大小按最大键分配。如果你触及 500k 上限,这通常意味着应切换到hashed_array。hashed_array— 适用于大多数场景的推荐默认布局。将属性存储在数组中,并使用哈希表将键映射到数组索引。速度几乎与hashed一样快,但内存效率更高,尤其是在属性较多时。hashed— 将整个字典存储在哈希表中。当属性很少时,它可能比hashed_array更快,但随着属性数量增加,内存占用也会更高。complex_key_hashed/complex_key_hashed_array— 当键无法 CAST 为UInt64时使用 (例如String键) 。它们与对应的非复杂键版本具有相同的性能权衡。sparse_hashed— 与hashed相比,以增加 CPU 开销为代价换取更低的内存占用。它很少是最佳选择——只有在仅有一个属性时才比较高效。在大多数情况下,hashed_array更合适。cache/ssd_cache— 仅缓存频繁访问的键。当完整数据集无法装入内存时,它们会很有用,但缓存未命中时,查找可能会访问数据源。对于延迟敏感型工作负载,不推荐使用。direct— 每次查找都直接查询数据源,不进行内存存储。适用于数据变化过于频繁而无法缓存,或字典太大而无法放入内存的情况。
监控字典使用情况
system.dictionaries 表跟踪内存占用和健康状态:
bytes_allocated— 字典占用的内存。字典以未压缩形式存储数据,因此该值可能会显著大于压缩后的表大小。hit_rate和found_rate— 可用于评估cache布局的效果。last_exception— 当字典加载或刷新失败时,请检查此项。