延迟物化
本文档描述了延迟物化的工作原理以及它如何融入 ClickHouse 更广泛的 I/O 优化堆栈。它提供了一个实际示例,展示了延迟物化如何提高查询性能。
延迟物化是在 ClickHouse 25.4 版本中引入的,并且默认情况下已启用。
概述
多年来,ClickHouse 引入了一系列分层优化,以积极减少 I/O。这些技术构成了其速度和效率的基础
| 优化 | 描述 |
|---|---|
| 列式存储 | 允许跳过查询不需要的整个列,并通过将相似的值组合在一起,从而在数据加载期间实现高压缩,最大限度地减少 I/O。 |
| 稀疏主索引 | 二级数据跳过索引 | 投影 | 通过识别哪些 颗粒 (行块) 可能会匹配 索引列 上的过滤器来修剪无关数据。这些技术在颗粒级别运行,可以单独使用或组合使用。 |
| PREWHERE | 即使对于非索引列上的过滤器,也检查匹配项,以便尽早跳过原本将被加载和丢弃的数据。它可以独立工作,也可以通过跳过不匹配所有列过滤器的行来完善索引选择的颗粒,从而补充颗粒修剪。 |
| 查询条件缓存 | 通过记住上次哪些颗粒匹配所有过滤器来加速重复查询。即使查询形状发生变化,ClickHouse 也可以跳过读取和过滤不匹配的颗粒。 |
虽然上述 I/O 优化可以显著减少读取的数据量,但它们仍然假定在运行排序、聚合或 LIMIT 等操作之前,必须加载通过 WHERE 子句的行的所有列。但是,如果某些列直到以后才需要,或者某些数据即使通过了 WHERE 子句,也从根本上不需要呢?这就是延迟物化的用武之地。它是一种正交的增强功能,可以完善 I/O 优化堆栈
- 索引与
PREWHERE结合使用,可确保仅处理匹配WHERE子句中列过滤器的行。 - 延迟物化在此基础上构建,延迟读取列,直到查询执行计划实际需要它们为止。即使在过滤之后,也仅立即加载下一操作(例如排序)所需的列。其他列将被推迟,并且由于
LIMIT的存在,通常只会部分读取,仅读取足以生成最终结果的内容。这使得延迟物化对于 Top N 查询特别有效,因为最终结果可能只需要来自某些(通常很大)列的少数行。
一个实际示例
我们强烈建议阅读博客文章 “ClickHouse 变得更懒(也更快):介绍延迟物化”,以深入了解延迟物化。下面的示例摘自上述博客文章,并在此处重现,以演示 ClickHouse 查询如何从 219 秒减少到仅 139 毫秒(速度提升 1576 倍),这得益于延迟物化。
为了从索引和 PREWHERE 中受益,查询需要过滤器,主键列用于索引,以及任何用于 PREWHERE 的列。延迟物化可以干净地叠加在上面,但与之前提到的其他优化不同,它还可以加速没有列过滤器的查询。
考虑以下示例查询,该查询查找具有最高有用投票数的亚马逊评论,无论日期、产品、评分或验证状态如何,并返回前 3 个评论及其标题、标题和全文。
首先运行查询(文件系统缓存为空),并将延迟物化禁用(使用 query_plan_optimize_lazy_materialization)
接下来,再次运行查询(再次使用空文件系统缓存),但这次启用延迟物化
通常,您不需要显式设置 query_plan_optimize_lazy_materialization = true 即可获得延迟物化的好处。它默认情况下已启用。
考虑在关闭和启用延迟物化时性能的差异
| 指标 | 延迟物化关闭 | 延迟物化开启 | 改进 |
|---|---|---|---|
| 经过的时间 | 219.071 秒 | 0.139 秒 | 快约 1576 倍 |
| 读取的数据 | 71.38 GB | 1.81 GB | 减少约 40 倍 |
| 峰值内存 | 1.11 GiB | 3.80 MiB | 减少约 300 倍 |
如何确认查询执行计划中的延迟物化
您可以通过使用 EXPLAIN 子句检查查询的逻辑执行计划来观察延迟物化的使用情况
您可以从底部到顶部读取操作符计划,并观察 ClickHouse 推迟读取三个大型字符串列,直到排序和限制之后。