查询缓存
查询缓存允许仅计算一次 SELECT 查询,并直接从缓存提供后续的执行结果。根据查询的类型,这可以显著降低 ClickHouse 服务器的延迟和资源消耗。
背景、设计和限制
查询缓存通常可以被视为事务一致性或不一致性缓存。
- 在事务一致性缓存中,数据库会在
SELECT查询的结果发生或可能发生变化时使缓存失效(丢弃)缓存的查询结果。在 ClickHouse 中,更改数据的操作包括表中的插入/更新/删除或合并的折叠。事务一致性缓存特别适合 OLTP 数据库,例如 MySQL(在 v8.0 之后移除了查询缓存)和 Oracle。 - 在事务不一致性缓存中,在假设所有缓存条目都分配了有效期限(例如 1 分钟),并且底层数据在此期间内仅发生少量变化的情况下,可以接受查询结果的轻微不准确性。这种方法总体上更适合 OLAP 数据库。例如,考虑一个报告工具中每小时的销售报告,该报告同时被多个用户访问。销售数据通常变化缓慢,数据库只需要计算一次报告(由第一个
SELECT查询表示)。后续查询可以直接从查询缓存提供。在此示例中,合理的有效期限可以是 30 分钟。
事务不一致性缓存传统上由客户端工具或代理包(例如 chproxy)与数据库交互来提供。因此,相同的缓存逻辑和配置经常被重复。使用 ClickHouse 的查询缓存,缓存逻辑移动到服务器端。这减少了维护工作并避免了冗余。
配置设置和用法
clickhouse-local 每次只运行一个查询。由于查询结果缓存没有意义,因此在 clickhouse-local 中禁用了查询结果缓存。
设置 use_query_cache 可用于控制是否应使用查询缓存来利用特定查询或当前会话的所有查询。例如,第一次执行查询
会将查询结果存储在查询缓存中。相同查询的后续执行(也使用参数 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_cache 和 enable_reads_from_query_cache(默认均为 true)更详细地配置缓存的使用方式。前者设置控制是否将查询结果存储在缓存中,而后者设置确定数据库是否应尝试从缓存中检索查询结果。例如,以下查询将仅被动地使用缓存,即尝试从中读取但不将其结果存储在其中
为了获得最大的控制力,通常建议仅使用特定查询提供设置 use_query_cache、enable_writes_to_query_cache 和 enable_reads_from_query_cache。也可以在用户或配置文件级别启用缓存(例如,通过 SET use_query_cache = true),但应记住,那时所有 SELECT 查询都可能返回缓存的结果。
可以使用语句 SYSTEM CLEAR QUERY CACHE 清除查询缓存。查询缓存的内容在系统表 system.query_cache 中显示。数据库启动后查询缓存命中和未命中的次数显示为系统表 system.events 中的事件“QueryCacheHits”和“QueryCacheMisses”。这两个计数器仅针对使用设置 use_query_cache = true 运行的 SELECT 查询进行更新,其他查询不会影响“QueryCacheMisses”。系统表 system.query_log 中的字段 query_cache_usage 显示每个执行的查询是否将查询结果写入或从查询缓存中读取。
查询缓存存在于每个 ClickHouse 服务器进程一次。但是,默认情况下,缓存结果不共享给用户。可以更改此设置(如下所示),但出于安全原因不建议这样做。
查询结果在查询缓存中通过其 抽象语法树 (AST) 引用。这意味着缓存不区分大小写,例如 SELECT 1 和 select 1 被视为相同的查询。为了使匹配更自然,与查询缓存和 输出格式 相关的所有查询级别设置都从 AST 中删除。
如果查询由于异常或用户取消而中止,则不会将任何条目写入查询缓存。
查询缓存的大小、最大缓存条目数以及单个缓存条目的最大大小(以字节和记录为单位)可以使用不同的 服务器配置选项 进行配置。
还可以使用 设置配置文件 和 设置约束 来限制单个用户的缓存使用量。更具体地说,您可以限制用户在查询缓存中分配的最大内存量(以字节为单位)和存储的查询结果的最大数量。为此,首先在 users.xml 中的用户配置文件中提供配置 query_cache_max_size_in_bytes 和 query_cache_max_entries,然后将这两个设置设为只读
要定义查询至少运行多长时间才能缓存其结果,可以使用设置 query_cache_min_query_duration。例如,查询的结果
仅当查询运行时间超过 5 秒时才会被缓存。也可以指定查询需要运行多少次才能缓存其结果 - 为此使用设置 query_cache_min_query_runs。
查询缓存中的条目在一段时间后(生存时间)会失效。默认情况下,此时间段为 60 秒,但可以使用会话、配置文件或查询级别设置 query_cache_ttl 指定不同的值。查询缓存会“延迟”驱逐条目,即当条目失效时,不会立即从缓存中删除。相反,当要将新条目插入查询缓存时,数据库会检查缓存是否有足够的可用空间。如果没有,数据库会尝试删除所有过期的条目。如果缓存仍然没有足够的可用空间,则不会插入新条目。
如果通过 HTTP 运行查询,则 ClickHouse 会使用缓存条目的年龄(以秒为单位)和到期时间戳设置 Age 和 Expires 标头。
默认情况下,查询缓存中的条目会被压缩。这降低了整体内存消耗,但代价是写入/读取查询缓存的速度较慢。要禁用压缩,请使用设置 query_cache_compress_entries。
有时,对于相同的查询,保持多个结果缓存是有用的。可以使用设置 query_cache_tag 实现此目的,该设置充当查询缓存条目的标签(或命名空间)。查询缓存将具有不同标签的相同查询的结果视为不同。
创建相同查询的三个不同查询缓存条目的示例
要仅从查询缓存中删除带有标签 tag 的条目,可以使用语句 SYSTEM CLEAR QUERY CACHE TAG 'tag'。
ClickHouse 以 max_block_size 行的块读取表数据。由于过滤、聚合等原因,结果块通常远小于“max_block_size”,但也有一些情况是结果块大得多。设置 query_cache_squash_partial_results(默认启用)控制结果块是否被压缩(如果它们很小)或拆分(如果它们很大)成“max_block_size”大小的块,然后插入到查询结果缓存中。这会降低写入查询缓存的性能,但可以提高缓存条目的压缩率,并在以后从查询缓存提供查询结果时提供更自然的块粒度。
因此,查询缓存为每个查询存储多个(部分)结果块。虽然这种行为是默认值,但可以使用设置 query_cache_squash_partial_results 禁用此行为。
此外,默认情况下,不缓存具有非确定性函数的查询的结果。这些函数包括
- 访问字典的函数:
dictGet()等。 - 用户自定义函数在 XML 定义中没有包含标签
<deterministic>true</deterministic>, - 返回当前日期或时间的函数:
now(),today(),yesterday()等, - 返回随机值的函数:
randomString(),fuzzBits()等, - 其结果依赖于查询处理过程中使用的块的大小和顺序:
nowInBlock()等,rowNumberInBlock(),runningDifference(),blockSize()等, - 依赖于环境的函数:
currentUser(),queryID(),getMacro()等。
要强制缓存包含非确定性函数的查询的结果,请使用设置 query_cache_nondeterministic_function_handling。
默认情况下,涉及系统表(例如 system.processes 或 information_schema.tables)的查询结果不会被缓存。要强制缓存包含系统表的查询的结果,请使用设置 query_cache_system_table_handling。
最后,出于安全原因,查询缓存中的条目不会在用户之间共享。例如,用户 A 不应能够通过运行与用户 B 相同的查询来绕过表上的行策略,而用户 B 没有此类策略。但是,如果需要,可以通过提供设置 query_cache_share_between_users 来标记缓存条目以便其他用户访问(即共享)。