> ## Documentation Index
> Fetch the complete documentation index at: https://private-7c7dfe99-parallel-read-in-order-multi-part.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# cache 字典布局

> 将字典存储在固定大小的内存缓存中。

`cached` 字典布局类型会将字典存储在一个具有固定单元数的缓存中。
这些单元保存的是经常使用的元素。

字典键的类型为 [UInt64](/zh/reference/data-types/int-uint)。

查找字典时，会先查缓存。对于每个数据块，凡是在缓存中未命中或已过期的键，都会通过 `SELECT attrs... FROM db.table WHERE id IN (k1, k2, ...)` 从源中请求。接收到的数据随后会写入缓存。

这适用于**查找**键，即 `dictGet` 和其他字典函数。通过 `SELECT ... FROM <dictionary>` 将字典**作为表读取**则有所不同：由于缓存不会记录哪些键存在，这种读取只会枚举当时恰好驻留在缓存中且保存有值的单元，而对键使用的 `WHERE` 是针对这些单元的普通过滤器，而不是要拉取的键列表。不在缓存中的键无法通过这种方式发现，无论 `WHERE` 如何书写。曾被查找过但在源中未找到的键同样不可见：缓存会将该次未命中记录为默认单元，而表读取会跳过默认单元。驻留单元也并非与源无关：已过期的单元会通过与 `dictGet` 相同的路径读取，因此会从源中重新请求——同步进行，或者在启用了 `allow_read_expired_keys` 时异步进行。

```sql theme={null}
CREATE DICTIONARY cache_dict (id UInt64, data String) PRIMARY KEY id
SOURCE(CLICKHOUSE(TABLE 'cache_src')) LIFETIME(MIN 0 MAX 900) LAYOUT(CACHE(SIZE_IN_CELLS 1000));

-- nothing is cached yet, so nothing comes back and the source is not queried
SELECT count() FROM cache_dict WHERE id IN (1, 2, 3);
0

-- looking the keys up populates the cache
SELECT dictGet('cache_dict', 'data', toUInt64(number + 1)) FROM numbers(3);

-- and now the same read sees them
SELECT count() FROM cache_dict WHERE id IN (1, 2, 3);
3
```

因此，cache 字典应通过字典函数访问。如果需要让任意键的查找始终访问源，请将 `dictGet` 与 [direct](/zh/reference/statements/create/dictionary/layouts/direct) 布局配合使用；该布局会在每次查找时查询源，不会缓存任何数据。请注意，对 `direct` 字典进行表读取同样不是按键拉取：`SELECT ... FROM <dictionary> WHERE key IN (...)` 会加载整个源，然后再进行过滤，因为 ClickHouse 不会将键过滤器下推到字典中。若要将字典作为表读取，请使用可容纳全部数据的布局，例如 [flat](/zh/reference/statements/create/dictionary/layouts/flat) 或 [hashed](/zh/reference/statements/create/dictionary/layouts/hashed)。

如果在字典中找不到键，则会创建缓存更新任务并将其加入更新队列。更新队列的属性可通过 `max_update_queue_size`、`update_queue_push_timeout_milliseconds`、`query_wait_timeout_milliseconds`、`max_threads_for_updates` 这些设置来控制。

对于 cache 字典，可以设置缓存中数据的过期 [lifetime](/zh/reference/statements/create/dictionary/lifetime)。如果某个单元中的数据自加载以来经过的时间超过了 `lifetime`，则该单元的值将不再使用，该键也会变为过期状态。下次需要使用该键时，会重新发起请求。此行为可通过 `allow_read_expired_keys` 设置进行配置。

这是所有字典存储方式中效率最低的一种。缓存的速度在很大程度上取决于设置是否正确以及具体的使用场景。cache 类型字典只有在命中率足够高时才能获得良好性能 (建议达到 99% 或更高) 。你可以在 [system.dictionaries](/zh/reference/system-tables/dictionaries) 表中查看平均命中率。

如果将 `allow_read_expired_keys` 设置为 1 (默认为 0) ，则字典支持异步更新。如果客户端请求的键都在缓存中，但其中部分已过期，则字典会先向客户端返回这些过期键，同时异步从源中重新请求它们。

要提升缓存性能，请使用带 `LIMIT` 的子查询，并从字典外部调用该函数。

支持所有类型的源。

设置示例：

<Tabs>
  <Tab title="DDL">
    ```sql theme={null}
    LAYOUT(CACHE(SIZE_IN_CELLS 1000000000))
    ```
  </Tab>

  <Tab title="配置文件">
    ```xml theme={null}
    <layout>
        <cache>
            <!-- 缓存大小，以单元数计。向上取整为 2 的幂。 -->
            <size_in_cells>1000000000</size_in_cells>
            <!-- 允许读取过期键。 -->
            <allow_read_expired_keys>0</allow_read_expired_keys>
            <!-- 更新队列的最大大小。 -->
            <max_update_queue_size>100000</max_update_queue_size>
            <!-- 将更新任务推入队列的最大超时时间（毫秒）。 -->
            <update_queue_push_timeout_milliseconds>10</update_queue_push_timeout_milliseconds>
            <!-- 等待更新任务完成的最大超时时间（毫秒）。 -->
            <query_wait_timeout_milliseconds>60000</query_wait_timeout_milliseconds>
            <!-- cache 字典更新的最大线程数。 -->
            <max_threads_for_updates>4</max_threads_for_updates>
        </cache>
    </layout>
    ```
  </Tab>
</Tabs>

<br />

请将缓存大小设置得足够大。你需要通过试验来确定单元数：

1. 先设置一个值。
2. 运行查询，直到缓存被完全填满。
3. 使用 `system.dictionaries` 表评估内存消耗。
4. 增加或减少单元数，直到达到所需的内存消耗。

<Note>
  不建议将 ClickHouse 用作此布局的源。字典查找需要随机点读，而这并不是 ClickHouse 优化的访问模式。
</Note>
