SELECT 查询只计算一次,后续相同查询的执行结果可直接从缓存中返回。
根据查询类型的不同,这可以显著降低 ClickHouse 服务器的延迟和资源消耗。
背景、设计与局限性
- 在事务一致的缓存中,如果
SELECT查询的结果已经变化 或可能发生变化,数据库就会使已缓存的查询结果失效 (丢弃) 。在 ClickHouse 中,会更改数据的操作包括对表执行 insert/update/delete,或发生 collapsing merges。事务一致的缓存尤其适用于 OLTP 数据库,例如 MySQL (已在 v8.0 之后移除 query cache) 以及 Oracle。 - 在事务不一致的缓存中,可以接受查询结果存在轻微误差,前提是所有缓存条目
都会被设置一个有效期,过期后即失效 (例如 1 分钟) ,并且底层数据在这段时间内变化很小。
这种方式整体上更适合 OLAP 数据库。一个事务不一致缓存已足够的例子是:
报表工具中的每小时销售报表被多个用户同时访问。销售数据的变化通常
足够缓慢,因此数据库只需计算一次报表 (即第一次
SELECT查询) 。后续查询可以 直接由查询缓存返回结果。在这个例子中,合理的有效期可以是 30 分钟。
配置设置与用法
clickhouse-local 一次只能运行一条查询。由于查询结果缓存没有实际意义,因此 clickhouse-local 中默认禁用查询结果缓存。
use_query_cache = true) ,将会
直接从缓存中读取已计算的结果并立即返回。
use_query_cache 以及所有其他与查询缓存相关的设置,只有对独立的 SELECT 语句才会生效。特别是,
对通过 CREATE VIEW AS SELECT [...] SETTINGS use_query_cache = true 创建的视图执行 SELECT 时,其结果不会被缓存,除非该 SELECT
语句在运行时指定了 SETTINGS use_query_cache = true。true) 更细致地配置缓存的使用方式。前者
控制是否将查询结果写入缓存,而后者则决定数据库是否尝试从缓存中读取查询
结果。例如,下面这个查询只会被动使用缓存,也就是说,它会尝试从中读取,但不会将其
结果写入缓存:
use_query_cache、enable_writes_to_query_cache 和
enable_reads_from_query_cache。也可以在用户或 profile 级别启用缓存 (例如通过 SET use_query_cache = true) ,但需要注意的是,这样一来,所有 SELECT 查询之后都可能返回缓存结果。
可以使用语句 SYSTEM CLEAR QUERY CACHE 清空查询缓存。查询缓存的内容显示在系统表
system.query_cache 中。自数据库启动以来的查询缓存命中和未命中次数,会作为事件
“QueryCacheHits” 和 “QueryCacheMisses” 显示在系统表 system.events 中。这两个计数器只会对
在设置 use_query_cache = true 下运行的 SELECT 查询进行更新,其他查询不会影响 “QueryCacheMisses”。系统表
system.query_log 中的字段 query_cache_usage 会显示每个已执行查询的结果是否被写入
查询缓存,或者是否从查询缓存中读取。系统表中的指标 QueryCacheEntries 和 QueryCacheBytes
system.metrics 显示查询缓存当前包含多少条目 / 字节。
每个 ClickHouse server process 都有一个查询缓存实例。不过,默认情况下,缓存结果不会在用户之间共享。这一点可以
更改 (见下文) ,但出于安全原因,不建议这样做。
查询结果在查询缓存中通过其查询的 抽象语法树 (AST) 来标识。
这意味着缓存不区分大小写,例如 SELECT 1 和 select 1 会被视为同一个查询。为了
让匹配更符合直觉,所有与查询缓存以及输出格式)相关的查询级设置
都会从 AST 中移除。
如果查询因异常或被用户取消而中止,则不会向查询缓存写入任何条目。
查询缓存的字节大小、缓存条目的最大数量,以及单个缓存条目的最大大小 (按字节和
记录数计) ,都可以通过不同的服务器配置选项进行配置。
users.xml 的用户 profile 中配置
query_cache_max_size_in_bytes 和
query_cache_max_entries,然后将这两个设置都设为
只读:
Age 和 Expires 请求头,其中包含该
缓存条目的缓存时长 (以秒为单位) 和过期时间戳。
查询缓存中的条目默认会被压缩。这样可以减少总体内存占用,但代价是写入查询缓存 / 从查询缓存读取
的速度会变慢。要禁用压缩,请使用设置 query_cache_compress_entries。
有时,让同一查询的多个结果同时保留在缓存中会很有用。这可以通过设置
query_cache_tag 来实现,它充当查询缓存条目的标签 (或命名空间) 。查询缓存
会将带有不同标签的同一查询结果视为不同结果。
为同一查询创建三个不同查询缓存条目的示例:
tag 的条目,可以使用语句 SYSTEM CLEAR QUERY CACHE TAG 'tag'。
ClickHouse 按 max_block_size 行一块读取表数据。由于过滤、aggregation
等操作,结果块通常会比 ‘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()等, - 结果取决于查询处理过程中所用内部 chunks 的大小和顺序的函数:
nowInBlock()等,rowNumberInBlock(),runningDifference(),blockSize()等, - 依赖环境的函数:
currentUser(),queryID(),getMacro()等。