**ClickHouse** 变得更“懒”也更快了:介绍延迟物化 (Lazy Materialization)
[正文]
想象一下,如果你在机场才得知自己不用出差了,省去了打包行李的麻烦,那该多好。 这就是 ClickHouse 现在对数据的处理方式。
ClickHouse 是目前速度最快的分析型数据库之一,其速度很大程度上来自于避免不必要的工作。扫描和处理的数据越少,查询运行速度就越快。 现在,它通过一项新的优化措施进一步推进了这一理念:延迟物化 (lazy materialization),它会延迟读取列数据,直到实际需要时才进行。
这种看似“懒惰”的行为,在实际工作负载中却非常有效,尤其是在对大型数据集进行排序并应用 LIMIT
子句的 Top N
查询中,这在可观测性和通用分析中是一种常见的模式。 在这些情况下,延迟物化可以显著加快性能,通常可以提高几个数量级。
剧透警告:我们将向您展示一个 ClickHouse 查询如何从 219 秒缩短到仅 139 毫秒——速度提高了 1576 倍——而无需更改任何一行 SQL 代码。 相同的查询。 相同的表。 相同的机器。 唯一改变的是? ClickHouse 读取数据的时间。
在这篇文章中,我们将介绍延迟物化的工作原理,以及它如何融入 ClickHouse 更广泛的 I/O 优化堆栈中。为了提供完整的图景,我们还将简要演示 ClickHouse I/O 效率的其他关键构建块,不仅突出延迟物化的作用,还突出它与已有的技术有何不同以及如何互补。
我们将首先描述 ClickHouse 已经使用的核心 I/O 节省技术,然后逐层地运行一个真实世界的查询,直到延迟物化启动并改变一切。
ClickHouse 中 I/O 效率的构建块
多年来,ClickHouse 引入了一系列分层优化,以积极减少 I/O。这些技术构成了其速度和效率的基础:
- 列式存储 允许跳过查询不需要的整个列,并通过将相似的值组合在一起实现高压缩,从而最大限度地减少数据加载期间的 I/O。
- 稀疏主索引、二级数据跳过索引 和 投影 通过识别哪些 granule(行块)可能与 索引列 上的过滤器匹配来修剪不相关的数据。 这些技术在 granule 级别上运行,可以单独或组合使用。
- PREWHERE 还会检查 非索引 列上的过滤器是否匹配,以便尽早跳过本来会被加载和丢弃的数据。 它可以独立工作,也可以细化索引选择的 granule,通过跳过与 所有 列过滤器不匹配的行来补充 granule 修剪。
- 查询条件缓存 (深度剖析) 通过记住上次哪些 granule 与所有过滤器匹配来加速重复查询。 然后,即使查询形状发生变化,ClickHouse 也可以跳过读取和过滤不匹配的 granule。 由于它只是缓存索引和 PREWHERE 过滤的结果,因此我们在此不再赘述。 我们在下面的所有测试中都禁用了它,以避免结果出现偏差。
这些技术,包括下面介绍的延迟物化,减少了查询处理 期间 的 I/O,这是本文的重点。 一种正交的方法是通过使用 增量 或 可刷新 物化视图 预先计算结果来减少表大小(和查询工作),我们在此不再赘述。
使用延迟物化完成堆栈
虽然上述 I/O 优化可以显著减少数据读取,但它们仍然假设在运行排序、聚合或 LIMIT
等操作之前,必须加载所有通过 WHERE
子句的行的所有列。 但是,如果某些列直到以后才需要,或者某些数据尽管通过了 WHERE
子句,但根本不需要怎么办?
这就是延迟物化发挥作用的地方。 这是一个正交增强,完善了 I/O 优化堆栈:
- 索引与 PREWHERE 结合使用,确保只处理与
WHERE
子句中的列过滤器匹配的行。 - 延迟物化在此基础上构建,它会延迟列读取,直到查询执行计划实际需要它们时才进行。 即使在过滤之后,也只会立即加载下一个操作(例如排序)所需的列。 其他列会被推迟,并且由于
LIMIT
,通常只会部分读取,仅足以生成最终结果。 这使得延迟物化对于 Top N 查询特别强大,因为最终结果可能只需要来自某些(通常很大)列的少量行。
这种细粒度的列处理之所以成为可能,是因为 ClickHouse 独立存储每个列。 在 面向行 的数据库中,所有列都一起读取,这种级别的延迟 I/O 根本不可行。
为了演示它的影响,我们现在将介绍一个真实世界的示例,并展示每一层优化如何发挥作用。
测试设置:数据集和机器
我们将使用 Amazon 客户评论 数据集,其中包含从 1995 年到 2015 年的大约 1.5 亿条产品评论。
我们正在 AWS m6i.8xlarge
EC2 实例上运行 ClickHouse 25.4,配置如下:
- 32 个 vCPU
- 128 GiB 内存
- 1 TiB gp3 SSD(使用默认设置:3000 IOPS,125 MiB/s 最大吞吐量 🐌)
- Ubuntu Linux 24.04
在该机器上,我们首先创建了 Amazon 评论表:
CREATE TABLE amazon.amazon_reviews
(
`review_date` Date CODEC(ZSTD(1)),
`marketplace` LowCardinality(String) CODEC(ZSTD(1)),
`customer_id` UInt64 CODEC(ZSTD(1)),
`review_id` String CODEC(ZSTD(1)),
`product_id` String CODEC(ZSTD(1)),
`product_parent` UInt64 CODEC(ZSTD(1)),
`product_title` String CODEC(ZSTD(1)),
`product_category` LowCardinality(String) CODEC(ZSTD(1)),
`star_rating` UInt8 CODEC(ZSTD(1)),
`helpful_votes` UInt32 CODEC(ZSTD(1)),
`total_votes` UInt32 CODEC(ZSTD(1)),
`vine` Bool CODEC(ZSTD(1)),
`verified_purchase` Bool CODEC(ZSTD(1)),
`review_headline` String CODEC(ZSTD(1)),
`review_body` String CODEC(ZSTD(1))
)
ENGINE = MergeTree
ORDERBY (review_date, product_category);
然后从托管在我们公共示例数据集 S3 存储桶中的 Parquet 文件加载数据集:
INSERT INTO amazon.amazon_reviews
SELECT * FROM s3Cluster('default', 'https://datasets-documentation.s3.eu-west-3.amazonaws.com/amazon_reviews/amazon_reviews_*.snappy.parquet');
我们检查加载后表的大小:
SELECT
formatReadableQuantity(sum(rows)) AS rows,
formatReadableSize(sum(data_uncompressed_bytes)) AS data_size,
formatReadableSize(sum(data_compressed_bytes)) AS compressed_size
FROM system.parts
WHERE active AND database = 'amazon' AND table = 'amazon_reviews';
结果如下:
┌─rows───────────┬─data_size─┬─compressed_size─┐
│ 150.96 million │ 70.47 GiB │ 30.05 GiB │
└────────────────┴───────────┴─────────────────┘
加载后,该表包含约 1.5 亿行,以及:
- 70 GiB 未压缩数据
- 使用 ZSTD(1) 压缩后约 30 GiB
ClickHouse 速度很快,但您的磁盘可能不是
对于 ClickHouse 来说,1.5 亿行数据根本不是挑战。 例如,以下查询对 helpful_votes
列(它不是表排序键的一部分)中的所有 1.5 亿个值进行排序,并返回前 3 个,在冷启动情况下仅用时 70 毫秒(预先 清除 了 OS 文件系统缓存),处理吞吐量为 21.5 亿行/秒:
SELECT helpful_votes
FROM amazon.amazon_reviews
ORDER BY helpful_votes DESC LIMIT 3;
结果如下:
┌─helpful_votes─┐
│ 47524 │
│ 41393 │
│ 41278 │
└───────────────┘
3 rows in set. Elapsed: 0.070 sec. Processed 150.96 million rows, 603.83 MB (2.15 billion rows/s., 8.61 GB/s.)
Peak memory usage: 3.59 MiB.
请注意,该查询没有从索引、PREWHERE 或其他 I/O 减少技术中获益,因为它没有过滤器。 但由于列式存储,ClickHouse 只读取 helpful_votes
列,而跳过其余列。
以下是另一个示例查询,它只是从单个 review_body
列中选择所有数据(使用冷文件系统缓存):
SELECT review_body
FROM amazon.amazon_reviews
FORMAT Null;
结果如下:
Query id: b9566386-047d-427c-a5ec-e90bee027b02
0 rows in set. Elapsed: 176.640 sec. Processed 150.96 million rows, 56.02 GB (854.61 thousand rows/s., 317.13 MB/s.)
Peak memory usage: 733.14 MiB.
😱 几乎 3 分钟! 尽管只读取了一列。
但瓶颈不是 ClickHouse,而是磁盘的吞吐量。 此查询扫描了一个更大的列,即 56 GB,而上一个示例中为 600 MB。 在我们的测试机器上,该机器具有 相对较慢的磁盘 和 32 个 CPU 核心,ClickHouse 使用了 32 个 并行流 来读取数据。 查询日志 确认 3 分钟的运行时间主要用于 等待读取系统调用:
SELECT round(ProfileEvents['DiskReadElapsedMicroseconds'] / 1e6) AS disk_read_seconds,
ProfileEvents['ConcurrencyControlSlotsAcquired'] AS parallel_streams,
formatReadableTimeDelta(round(disk_read_seconds / parallel_streams), 'seconds') AS time_per_stream
FROM system.query_log
WHERE query_id = 'b9566386-047d-427c-a5ec-e90bee027b02' AND type = 'QueryFinish';
结果如下:
┌─disk_read_seconds─┬─parallel_streams─┬─time_per_stream─┐
│ 5512 │ 32 │ 172 seconds │
└───────────────────┴──────────────────┴─────────────────┘
显然,蛮力扫描并不理想,尤其是在冷缓存的情况下。 让我们给 ClickHouse 一些可以利用的东西。
更实际的查询——优化很重要
尽管发生了机场 闹剧,我仍然决定去海滩度假,这意味着我的电子阅读器只能装最好的书。 因此,我请 ClickHouse 帮助我查找自 2010 年以来购买的数字电子书评价最高的 5 星验证评论,显示有用的投票数、书名、评论标题和评论本身:
SELECT
helpful_votes,
product_title,
review_headline,
review_body
FROM amazon.amazon_reviews
WHERE review_date >= '2010-01-01' AND product_category = 'Digital_Ebook_Purchase' AND verified_purchase
AND star_rating > 4
ORDER BY helpful_votes DESC
LIMIT 3
FORMAT Vertical;
结果如下:
Row 1:
──────
helpful_votes: 6376
product_title: Wheat Belly: Lose the Wheat, Lose the Weight, and Find Your Path Back to Health
review_headline: Overweight? Diabetic? Got High Blood Pressure, Arthritis? Get this Book!
review_body: I've been following Dr. Davis' heart scan blog for the past ...
Row 2:
──────
helpful_votes: 4149
product_title: The Life-Changing Magic of Tidying Up: The Japanese Art of Decluttering and Organizing
review_headline: Truly life changing
review_body: I rarely write reviews, but this book truly sparked somethin...
Row 3:
──────
helpful_votes: 2623
product_title: The Fast Metabolism Diet: Eat More Food and Lose More Weight
review_headline: Fantastic Results **UPDATED 1/23/2015**
review_body: I have been on this program for 7 days so far. I know it ma...
上面的查询选择了四列,包括三列(product_title
、review_headline
、review_body
),它们是表中最大的列:
SELECT
name AS column,
formatReadableSize(sum(data_uncompressed_bytes)) AS data_size,
formatReadableSize(sum(data_compressed_bytes)) AS compressed_size
FROM system.columns
WHERE database = 'amazon' AND table = 'amazon_reviews'
GROUP BY name
ORDER BY sum(data_uncompressed_bytes) DESC;
结果如下:
┌─column────────────┬─data_size──┬─compressed_size─┐
│ review_body │ 51.13 GiB │ 21.60 GiB │
│ product_title │ 8.12 GiB │ 3.53 GiB │
│ review_headline │ 3.38 GiB │ 1.58 GiB │
│ review_id │ 2.07 GiB │ 1.35 GiB │
│ product_id │ 1.55 GiB │ 720.97 MiB │
│ customer_id │ 1.12 GiB │ 524.35 MiB │
│ product_parent │ 1.12 GiB │ 571.63 MiB │
│ helpful_votes │ 575.86 MiB │ 72.11 MiB │
│ total_votes │ 575.86 MiB │ 83.50 MiB │
│ review_date │ 287.93 MiB │ 239.43 KiB │
│ marketplace │ 144.51 MiB │ 414.92 KiB │
│ product_category │ 144.25 MiB │ 838.96 KiB │
│ star_rating │ 143.96 MiB │ 41.99 MiB │
│ verified_purchase │ 143.96 MiB │ 20.50 MiB │
│ vine │ 1.75 MiB │ 844.89 KiB │
└───────────────────┴────────────┴─────────────────┘
示例查询涉及 60+ GiB(未压缩)数据。 正如我们之前展示的那样,即使有 32 个并行流,仅仅从(相对较慢的)磁盘读取这些数据也需要 3 分钟以上,并且使用冷缓存。
但该查询包括对多个列的过滤器(review_date
、product_category
、verified_purchase
和 star_rating
),以及在按 helpful_votes
排序后应用的 LIMIT
。 这是 ClickHouse 分层 I/O 优化的完美设置:
- 索引 修剪与主/排序键(
review_date
、product_category
)上的过滤器不匹配的行。 - PREWHERE 将过滤进一步下推,并修剪与 所有 列过滤器不匹配的行。
- 延迟物化 延迟加载大的
SELECT
列(product_title
、review_headline
、review_body
),直到实际需要它们时才加载——在排序和应用LIMIT
之后。 理想情况下,大部分大型列数据根本不会被读取。
每一层都会进一步减少 I/O。 它们共同减少了数据读取、内存使用和查询时间。 让我们看看这会产生多大的差异,一次一层。
使用冷启动的 OS 级文件系统缓存
在以下各节中,我们在每次查询运行之前,使用 echo 3 | sudo tee /proc/sys/vm/drop_caches >/dev/null
清除 OS 级文件系统(页面)缓存。
在 Linux 命令行上。 这模拟了最坏情况,并确保结果反映了实际的磁盘读取,而不是缓存数据。
没有捷径:基准完全扫描
在我们引入优化之前,让我们看看当 ClickHouse 在没有任何捷径的情况下运行查询时会发生什么——没有索引、没有 PREWHERE、没有延迟物化。
为此,我们在没有排序/主键的表版本上运行示例查询,这意味着它不会从任何基于索引的优化中受益。 以下命令创建了该基准表:
CREATE TABLE amazon.amazon_reviews_no_pk
Engine = MergeTree
ORDER BY ()
AS SELECT * FROM amazon.amazon_reviews;
现在,我们在基准表上运行示例查询,禁用 PREWHERE 和延迟物化:
SELECT
helpful_votes,
product_title,
review_headline,
review_body
FROM amazon.amazon_reviews_no_pk
WHERE review_date >= '2010-01-01' AND product_category = 'Digital_Ebook_Purchase' AND verified_purchase
AND star_rating > 4
ORDER BY helpful_votes DESC
LIMIT 3
FORMAT Null
SETTINGS
optimize_move_to_prewhere = false,
query_plan_optimize_lazy_materialization = false;
结果如下:
3 rows in set. Elapsed: 219.508 sec. Processed 150.96 million rows, 72.13 GB (687.71 thousand rows/s., 328.60 MB/s.)
Peak memory usage: 953.25 MiB.
① 查询流式传输了所有 1.5 亿行——组织成 granule(ClickHouse 中最小的处理单元,每个单元默认覆盖 8192 行)——② 从磁盘到 ③ 内存的 8 个所需列,处理了 72 GB 的数据,耗时 220 秒,并且峰值内存使用量为 953 MiB:
ClickHouse 以 流式方式 处理表数据,以增量方式读取和操作 granule 块,而不是一次将所有数据加载到内存中。 这就是为什么即使是处理了 72 GB 数据的上述查询,峰值内存使用量也保持在 1 GiB 以下。
设置基准后,让我们看看第一层优化如何改进情况。
① 启用主索引
显然,扫描整个数据集远非最佳。 让我们开始应用 ClickHouse 的优化,从主索引开始。 我们在原始表上运行示例查询,仍然禁用 PREWHERE 和延迟物化,原始表使用 (review_date, product_category)
作为其复合排序(主)键:
SELECT
helpful_votes,
product_title,
review_headline,
review_body
FROM amazon.amazon_reviews
WHERE review_date >= '2010-01-01' AND product_category = 'Digital_Ebook_Purchase' AND verified_purchase
AND star_rating > 4
ORDER BY helpful_votes DESC
LIMIT 3
FORMAT Null
SETTINGS
optimize_move_to_prewhere = false,
query_plan_optimize_lazy_materialization = false;
结果如下:
0 rows in set. Elapsed: 95.865 sec. Processed 53.01 million rows, 27.67 GB (552.98 thousand rows/s., 288.68 MB/s.)
Peak memory usage: 629.00 MiB.
由于查询包括 ① 对表复合排序(主)键的过滤器,ClickHouse ② 加载并评估 稀疏主索引 以 ③ 仅选择主键列中可能包含匹配行的 granule。 然后将这些可能相关的 granule 与查询所需的任何其他列中位置对齐的 granule 一起 ④ 流式传输到内存中。 其余过滤器在此步骤之后应用:
因此,只有来自八个所需列的 5300 万行从磁盘流式传输到内存,处理 28 GB 而不是 72 GB 的数据,并将运行时间缩短一半以上:96 秒 vs. 220 秒。
主索引根据主键列上的过滤器修剪 granule。
但是,ClickHouse 仍然加载与匹配的键列 granule 位置对齐的所有其他列 granule,即使非键列上的过滤器稍后将它们排除在外。 这意味着仍在读取和处理不必要的数据。
为了解决这个问题,我们现在启用 PREWHERE。
② 添加 PREWHERE
我们再次运行相同的查询,这次启用了 PREWHERE(但仍然没有延迟物化)。 PREWHERE 增加了额外的效率层,在从磁盘读取非过滤器列之前过滤掉不相关的数据:
SELECT
helpful_votes,
product_title,
review_headline,
review_body
FROM amazon.amazon_reviews
WHERE review_date >= '2010-01-01' AND product_category = 'Digital_Ebook_Purchase' AND verified_purchase
AND star_rating > 4
ORDER BY helpful_votes DESC
LIMIT 3
FORMAT Null
SETTINGS
optimize_move_to_prewhere = true,
query_plan_optimize_lazy_materialization = false;
结果如下:
0 rows in set. Elapsed: 61.148 sec. Processed 53.01 million rows, 16.28 GB (866.94 thousand rows/s., 266.24 MB/s.)
Peak memory usage: 583.30 MiB.
启用 PREWHERE 后,查询处理了相同的 5300 万行,但读取的列数据明显减少,即 16.28 GB vs. 27.67 GB,并且速度提高了 36%(61 秒 vs. 96 秒),同时还略微降低了峰值内存使用量。
为了理解这种改进,让我们简要介绍一下 PREWHERE 如何改变 ClickHouse 处理查询的方式。
ClickHouse 不是预先流式传输所有选定的列 granule,而是通过 ① 仅加载索引分析识别的主键列 granule 来开始 PREWHERE 处理,以检查哪些 granule 实际包含匹配项。 在这种情况下,所有选定的 granule 都匹配,因此 ② 选择下一个过滤器列(verified_purchase
)的位置对齐 granule 以进行进一步过滤:
接下来,ClickHouse ① 读取选定的 verified_purchase
列 granule 以评估过滤器 verified_purchase
(它是 verified_purchase == True
的快捷方式)。
在这种情况下,四个 granule 中有三个包含匹配行,因此仅 ② 选择它们的与下一个过滤器列(star_rating
)的位置对齐的 granule 以进行进一步处理:
最后,ClickHouse 读取来自 star_rating
列的三个选定的 granule 以评估最后一个过滤器 star_rating > 4
。
三个 granule 中的两个包含匹配行,因此仅选择来自剩余列(helpful_votes
、product_title
、review_headline
和 review_body
)的位置对齐的 granule 以进行进一步处理:
这样,PREWHERE 处理就完成了。
ClickHouse 不是加载主索引选择的所有列 granule,然后应用剩余的过滤器,而是提前预先过滤选定的数据——因此得名。 ClickHouse 一次评估一列的过滤器,使用 基于成本的方法——通常从读取成本最低的列开始——并且仅为通过每个步骤的行加载数据。 这逐步缩小数据集,在查询运行排序、聚合、
LIMIT
和SELECT
等主要操作之前减少 I/O。
请注意,PREWHERE 也可以独立于索引工作。 如果查询仅对非索引列有过滤器,它仍然可以通过提前跳过不匹配的行来帮助减少 I/O。
PREWHERE 过滤后的步骤
在 PREWHERE 过滤之后,ClickHouse 继续 ① 加载选定的数据,② 对其进行排序,以及 ③ 应用 LIMIT 子句:
到目前为止,我们添加的每一层都在缩短查询时间,跳过不必要的数据,减少 I/O,并简化工作流程。
从需要 220 秒的完全扫描开始,我们已经缩短到了 61 秒。 但我们还没有完成。 最后一层带来了最大的减少。
③ 激活延迟物化
让我们看看当延迟物化加入堆栈时会发生什么。 我们最后一次运行查询,启用所有 I/O 优化,包括延迟物化。
SELECT
helpful_votes,
product_title,
review_headline,
review_body
FROM amazon.amazon_reviews
WHERE review_date >= '2010-01-01' AND product_category = 'Digital_Ebook_Purchase' AND verified_purchase
AND star_rating > 4
ORDER BY helpful_votes DESC
LIMIT 3
FORMAT Null
SETTINGS
optimize_move_to_prewhere = true,
query_plan_optimize_lazy_materialization = true;
结果如下:
0 rows in set. Elapsed: 0.181 sec. Processed 53.01 million rows, 807.55 MB (292.95 million rows/s., 4.46 GB/s.)
Peak memory usage: 3.88 MiB.
😮 从 61 秒到 181 毫秒,速度提高了 338 倍。
ClickHouse 处理了相同的 5300 万行,但读取的列数据减少了 20 倍,使用的内存减少了 150 倍,并且在你眨眼之前就完成了。
让我们看看内部发生了什么。
解释很简单:
在 PREWHERE 过滤之后,ClickHouse 不会 立即 加载所有剩余的列。
相反,它仅加载接下来需要的内容。 由于下一步是按 helpful_votes
排序并应用 LIMIT,因此 ClickHouse ① 仅加载选定的(和经过 PREWHERE 过滤的)helpful_votes
granule,② 对其行进行排序,③ 应用 LIMIT,然后 ④ 从 大型 product_title
、review_headline
和 review_body
列中加载相应的行:
就这样,最后一层就位了,将执行时间从 220 秒缩短到仅 181 毫秒。 相同的查询。 相同的表。 相同的机器。 相同的慢速磁盘……速度快了 1215 倍。 我们所做的只是更改了读取数据的方式和时间。
在此示例中,延迟物化提供了最大的收益,因为查询选择了大型文本列,并且由于延迟物化,最终只需要其中的 3 行。 但根据数据集和查询形状,早期优化(如索引或 PREWHERE)可能会产生更大的节省。 这些技术协同工作,每种技术都以不同的方式减少 I/O。
没有过滤器的速度:隔离的延迟物化
要从索引和 PREWHERE 中受益,查询需要过滤器,对主键列进行索引,并对任何列进行 PREWHERE。 如上所示,延迟物化干净利落地分层在顶部,但与其他优化不同,它还可以加速没有任何列过滤器的查询。
为了演示这一点,我们从示例查询中删除所有过滤器,以查找有帮助投票数最多的评论,而不管日期、产品、评分或验证状态如何,并返回前 3 个评论及其标题、标题和全文。
我们首先运行该查询(使用 冷文件系统缓存),禁用延迟物化:
SELECT
helpful_votes,
product_title,
review_headline,
review_body
FROM amazon.amazon_reviews
ORDER BY helpful_votes DESC
LIMIT 3
FORMAT Vertical
SETTINGS
query_plan_optimize_lazy_materialization = false;
结果如下:
Row 1:
──────
helpful_votes: 47524
product_title: Kindle: Amazon's Original Wireless Reading Device (1st generation)
review_headline: Why and how the Kindle changes everything
review_body: This is less a \"pros and cons\" review than a hopefully use...
Row 2:
──────
helpful_votes: 41393
product_title: BIC Cristal For Her Ball Pen, 1.0mm, Black, 16ct (MSLP16-Blk)
review_headline: FINALLY!
review_body: Someone has answered my gentle prayers and FINALLY designed ...
Row 3:
──────
helpful_votes: 41278
product_title: The Mountain Kids 100% Cotton Three Wolf Moon T-Shirt
review_headline: Dual Function Design
review_body: This item has wolves on it which makes it intrinsically swee...
0 rows in set. Elapsed: 219.071 sec. Processed 150.96 million rows, 71.38 GB (689.08 thousand rows/s., 325.81 MB/s.)
Peak memory usage: 1.11 GiB.
现在我们重新运行该查询(同样使用冷文件系统缓存),但这次启用了延迟物化:
SELECT
helpful_votes,
product_title,
review_headline,
review_body
FROM amazon.amazon_reviews
ORDER BY helpful_votes DESC
LIMIT 3
FORMAT Vertical
SETTINGS
query_plan_optimize_lazy_materialization = true;
结果如下:
Row 1:
──────
helpful_votes: 47524
product_title: Kindle: Amazon's Original Wireless Reading Device (1st generation)
review_headline: Why and how the Kindle changes everything
review_body: This is less a \"pros and cons\" review than a hopefully use...
Row 2:
──────
helpful_votes: 41393
product_title: BIC Cristal For Her Ball Pen, 1.0mm, Black, 16ct (MSLP16-Blk)
review_headline: FINALLY!
review_body: Someone has answered my gentle prayers and FINALLY designed ...
Row 3:
──────
helpful_votes: 41278
product_title: The Mountain Kids 100% Cotton Three Wolf Moon T-Shirt
review_headline: Dual Function Design
review_body: This item has wolves on it which makes it intrinsically swee...
0 rows in set. Elapsed: 0.139 sec. Processed 150.96 million rows, 1.81 GB (1.09 billion rows/s., 13.06 GB/s.)
Peak memory usage: 3.80 MiB.
速度提高了 1576 倍 ——从 219 秒到仅 139 毫秒——读取的数据减少了 40 倍,内存使用量降低了 300 倍。