跳至主要内容

查询缓存

查询缓存允许仅计算一次 SELECT 查询,并直接从缓存中提供相同查询的后续执行。根据查询的类型,这可以显著减少 ClickHouse 服务器的延迟和资源消耗。

背景、设计和限制

查询缓存通常可以被视为事务一致的或不一致的。

  • 在事务一致的缓存中,如果 SELECT 查询的结果发生变化或可能发生变化,数据库将使缓存的查询结果失效(丢弃)。在 ClickHouse 中,更改数据的操作包括在/更新/删除表中的/从表中的插入/更新/删除或合并折叠。事务一致的缓存特别适用于 OLTP 数据库,例如 MySQL(在 v8.0 之后删除了查询缓存)和 Oracle
  • 在事务不一致的缓存中,在假设所有缓存条目在过期后(例如 1 分钟)都被分配了一个有效期,并且基础数据在此期间变化很小的情况下,接受查询结果中的轻微不准确性。这种方法总体上更适合 OLAP 数据库。例如,在报告工具中,多个用户同时访问每小时的销售报告,在这种情况下,事务不一致的缓存就足够了。销售数据通常变化缓慢,因此数据库只需要计算一次报表(由第一个 SELECT 查询表示)。后续查询可以直接从查询缓存中提供服务。在此示例中,合理的有效期可以为 30 分钟。

事务不一致的缓存传统上由与数据库交互的客户端工具或代理包提供。因此,相同的缓存逻辑和配置经常被复制。使用 ClickHouse 的查询缓存,缓存逻辑转移到服务器端。这减少了维护工作并避免了冗余。

配置设置和用法

注意

在 ClickHouse Cloud 中,您必须使用 查询级别设置 来编辑查询缓存设置。目前不支持编辑 配置级别设置

设置 use_query_cache 可用于控制特定查询或当前会话的所有查询是否应使用查询缓存。例如,查询的第一次执行

SELECT some_expensive_calculation(column_1, column_2)
FROM table
SETTINGS use_query_cache = true;

将查询结果存储在查询缓存中。后续执行相同的查询(也使用参数 use_query_cache = true)将从缓存中读取计算结果并立即返回。

注意

设置 use_query_cache 和所有其他与查询缓存相关的设置仅对独立的 SELECT 语句有效。特别是,由 CREATE VIEW AS SELECT [...] SETTINGS use_query_cache = true 创建的视图的 SELECT 结果不会被缓存,除非 SELECT 语句以 SETTINGS use_query_cache = true 运行。

可以使用设置 enable_writes_to_query_cacheenable_reads_from-query-cache(默认情况下均为 true)更详细地配置缓存的使用方式。前者控制是否将查询结果存储在缓存中,而后者确定数据库是否应尝试从缓存中检索查询结果。例如,以下查询将仅被动地使用缓存,即尝试从中读取但不在其中存储其结果

SELECT some_expensive_calculation(column_1, column_2)
FROM table
SETTINGS use_query_cache = true, enable_writes_to_query_cache = false;

为了实现最大程度的控制,通常建议仅对特定查询提供设置 use_query_cacheenable_writes_to_query_cacheenable_reads_from_query_cache。也可以在用户或配置文件级别启用缓存(例如,通过 SET use_query_cache = true),但应记住,然后所有 SELECT 查询都可能返回缓存的结果。

可以使用语句 SYSTEM DROP QUERY CACHE 清除查询缓存。查询缓存的内容显示在系统表 system.query_cache 中。自数据库启动以来查询缓存命中和未命中的次数显示为系统表 system.events 中的事件“QueryCacheHits”和“QueryCacheMisses”。这两个计数器仅针对以设置 use_query_cache = true 运行的 SELECT 查询更新,其他查询不会影响“QueryCacheMisses”。系统表 system.query_log 中的字段 query_cache_usage 显示每个执行的查询的结果是写入还是读取自查询缓存。系统表 system.asynchronous_metrics 中的异步指标“QueryCacheEntries”和“QueryCacheBytes”显示查询缓存当前包含多少个条目/字节。

每个 ClickHouse 服务器进程只有一个查询缓存。但是,默认情况下,缓存结果不会在用户之间共享。这可以更改(见下文),但出于安全原因,不建议这样做。

查询结果在查询缓存中通过其查询的 抽象语法树 (AST) 来引用。这意味着缓存与大小写无关,例如 SELECT 1select 1 被视为相同的查询。为了使匹配更自然,与查询缓存相关的所有查询级别设置都从 AST 中删除。

如果查询因异常或用户取消而中止,则不会将任何条目写入查询缓存。

查询缓存的大小(以字节为单位)、缓存条目的最大数量以及单个缓存条目的最大大小(以字节和记录为单位)可以使用不同的 服务器配置选项 进行配置。

<query_cache>
<max_size_in_bytes>1073741824</max_size_in_bytes>
<max_entries>1024</max_entries>
<max_entry_size_in_bytes>1048576</max_entry_size_in_bytes>
<max_entry_size_in_rows>30000000</max_entry_size_in_rows>
</query_cache>

还可以使用 设置配置文件设置约束 来限制单个用户的缓存使用。更具体地说,您可以限制用户在查询缓存中分配的最大内存量(以字节为单位)和存储的查询结果的最大数量。为此,首先在 users.xml 中的用户配置文件中提供配置 query_cache_max_size_in_bytesquery_cache_max_entries,然后将这两个设置设为只读

<profiles>
<default>
<!-- The maximum cache size in bytes for user/profile 'default' -->
<query_cache_max_size_in_bytes>10000</query_cache_max_size_in_bytes>
<!-- The maximum number of SELECT query results stored in the cache for user/profile 'default' -->
<query_cache_max_entries>100</query_cache_max_entries>
<!-- Make both settings read-only so the user cannot change them -->
<constraints>
<query_cache_max_size_in_bytes>
<readonly/>
</query_cache_max_size_in_bytes>
<query_cache_max_entries>
<readonly/>
<query_cache_max_entries>
</constraints>
</default>
</profiles>

要定义查询至少必须运行多长时间才能将其结果缓存,可以使用设置 query_cache_min_query_duration。例如,查询的结果

SELECT some_expensive_calculation(column_1, column_2)
FROM table
SETTINGS use_query_cache = true, query_cache_min_query_duration = 5000;

仅在查询运行时间超过 5 秒时才会被缓存。还可以指定查询需要运行多少次才能缓存其结果 - 为此,请使用设置 query_cache_min_query_runs

查询缓存中的条目在一段时间后(生存时间)会失效。默认情况下,此时间段为 60 秒,但可以在会话、配置文件或查询级别使用设置 query_cache_ttl 指定不同的值。

查询缓存中的条目默认情况下会被压缩。这减少了整体内存消耗,但代价是写入/读取查询缓存的速度变慢。要禁用压缩,请使用设置 query_cache_compress_entries

有时,将同一查询的多个结果保留在缓存中很有用。这可以通过使用设置 query_cache_tag 来实现,该设置充当查询缓存条目的标签(或命名空间)。查询缓存将具有不同标签的相同查询的结果视为不同的结果。

为相同查询创建三个不同查询缓存条目的示例

SELECT 1 SETTINGS use_query_cache = true; -- query_cache_tag is implicitly '' (empty string)
SELECT 1 SETTINGS use_query_cache = true, query_cache_tag = 'tag 1';
SELECT 1 SETTINGS use_query_cache = true, query_cache_tag = 'tag 2';

要仅从查询缓存中删除带有标签 tag 的条目,可以使用语句 SYSTEM DROP QUERY CACHE TAG 'tag'

ClickHouse 以块的形式读取表数据,每个块包含 max_block_size 行。由于过滤、聚合等操作,结果块通常比 'max_block_size' 小得多,但也有一些情况结果块会大得多。设置 query_cache_squash_partial_results(默认启用)控制是否压缩结果块(如果它们很小)或将其拆分为大小为 'max_block_size' 的块(如果它们很大),然后再插入查询结果缓存。这样做会降低写入查询缓存的性能,但可以提高缓存条目的压缩率,并在稍后从查询缓存提供查询结果时提供更自然的块粒度。

因此,查询缓存为每个查询存储多个(部分)结果块。虽然这种行为是良好的默认设置,但可以使用设置 query_cache_squash_partial_results 来抑制它。

此外,包含非确定性函数的查询结果默认情况下不会被缓存。此类函数包括

要强制缓存包含非确定性函数的查询结果,请使用设置 query_cache_nondeterministic_function_handling

涉及系统表的查询结果(例如 system.processesinformation_schema.tables)默认情况下不会被缓存。要强制缓存包含系统表的查询结果,请使用设置 query_cache_system_table_handling

注意

在 ClickHouse v23.11 之前,设置 'query_cache_store_results_of_queries_with_nondeterministic_functions = 0 / 1' 控制是否缓存包含非确定性结果的查询结果。在较新的 ClickHouse 版本中,此设置已过时,不再起作用。

最后,由于安全原因,查询缓存中的条目不会在用户之间共享。例如,用户 A 不应该能够通过运行与另一个用户 B 相同的查询来绕过表的行策略,而用户 B 对该表没有这样的策略。但是,如果需要,可以通过提供设置 query_cache_share_between_users 来将缓存条目标记为可供其他用户访问(即共享)。