cached 字典布局类型会将字典存储在一个具有固定单元数的缓存中。
这些单元保存的是经常使用的元素。
字典键的类型为 UInt64。
查找字典时,会先查缓存。对于每个数据块,凡是在缓存中未命中或已过期的键,都会通过 SELECT attrs... FROM db.table WHERE id IN (k1, k2, ...) 从源中请求。接收到的数据随后会写入缓存。
这适用于查找键,即 dictGet 和其他字典函数。通过 SELECT ... FROM <dictionary> 将字典作为表读取则有所不同:由于缓存不会记录哪些键存在,这种读取只会枚举当时恰好驻留在缓存中且保存有值的单元,而对键使用的 WHERE 是针对这些单元的普通过滤器,而不是要拉取的键列表。不在缓存中的键无法通过这种方式发现,无论 WHERE 如何书写。曾被查找过但在源中未找到的键同样不可见:缓存会将该次未命中记录为默认单元,而表读取会跳过默认单元。驻留单元也并非与源无关:已过期的单元会通过与 dictGet 相同的路径读取,因此会从源中重新请求——同步进行,或者在启用了 allow_read_expired_keys 时异步进行。
dictGet 与 direct 布局配合使用;该布局会在每次查找时查询源,不会缓存任何数据。请注意,对 direct 字典进行表读取同样不是按键拉取:SELECT ... FROM <dictionary> WHERE key IN (...) 会加载整个源,然后再进行过滤,因为 ClickHouse 不会将键过滤器下推到字典中。若要将字典作为表读取,请使用可容纳全部数据的布局,例如 flat 或 hashed。
如果在字典中找不到键,则会创建缓存更新任务并将其加入更新队列。更新队列的属性可通过 max_update_queue_size、update_queue_push_timeout_milliseconds、query_wait_timeout_milliseconds、max_threads_for_updates 这些设置来控制。
对于 cache 字典,可以设置缓存中数据的过期 lifetime。如果某个单元中的数据自加载以来经过的时间超过了 lifetime,则该单元的值将不再使用,该键也会变为过期状态。下次需要使用该键时,会重新发起请求。此行为可通过 allow_read_expired_keys 设置进行配置。
这是所有字典存储方式中效率最低的一种。缓存的速度在很大程度上取决于设置是否正确以及具体的使用场景。cache 类型字典只有在命中率足够高时才能获得良好性能 (建议达到 99% 或更高) 。你可以在 system.dictionaries 表中查看平均命中率。
如果将 allow_read_expired_keys 设置为 1 (默认为 0) ,则字典支持异步更新。如果客户端请求的键都在缓存中,但其中部分已过期,则字典会先向客户端返回这些过期键,同时异步从源中重新请求它们。
要提升缓存性能,请使用带 LIMIT 的子查询,并从字典外部调用该函数。
支持所有类型的源。
设置示例:
- DDL
- 配置文件
请将缓存大小设置得足够大。你需要通过试验来确定单元数:
- 先设置一个值。
- 运行查询,直到缓存被完全填满。
- 使用
system.dictionaries表评估内存消耗。 - 增加或减少单元数,直到达到所需的内存消耗。
不建议将 ClickHouse 用作此布局的源。字典查找需要随机点读,而这并不是 ClickHouse 优化的访问模式。