字典
ClickHouse 中的字典提供了一种来自各种 内部和外部来源 的内存 键值 表示,优化超低延迟查找查询。
字典的用途包括:
- 提高查询性能,特别是在与
JOIN
一起使用时 - 在不中断摄取过程的情况下动态丰富摄取的数据

使用字典加速 JOIN
字典可以用来加速特定类型的 JOIN
:LEFT ANY
类型,其中连接键需要匹配底层键值存储的键属性。

如果是这种情况,ClickHouse 可以利用字典执行 直接连接。这是 ClickHouse 的最快连接算法,当右侧表的 表引擎 支持低延迟键值请求时适用。ClickHouse 具有三个提供此功能的表引擎:Join(基本上是一个预计算的哈希表)、EmbeddedRocksDB 和 Dictionary。我们将描述基于字典的方法,但所有三个引擎的机制是相同的。
直接连接算法要求右表由字典支持,使得待连接的数据在内存中以低延迟的键值数据结构的形式存在。
示例
使用 Stack Overflow 数据集,我们来回答这个问题: Hacker News 上有关 SQL 的最具争议的帖子是什么?
我们将争议定义为帖子有相似的支持和反对票数。我们计算这个绝对差值,接近 0 的值意味着更大的争议。我们假设帖子必须至少有 10 个支持和反对票 - 没有人投票的帖子并不是很有争议。
在数据标准化之后,这个查询目前需要使用 posts
和 votes
表的 JOIN
:
在
JOIN
的右侧使用较小的数据集:这个查询看起来可能比必要的要冗长,因为PostId
的过滤发生在外部和子查询中。这是一种性能优化,确保查询响应时间快速。为了最佳性能,请始终确保JOIN
的右侧是较小的集合,并尽可能小。有关优化 JOIN 性能和了解可用算法的提示,我们推荐 这一系列博文。
虽然这个查询很快,但它依赖于我们仔细编写 JOIN
来实现良好的性能。理想情况下,我们可以在查看 UpVote
和 DownVote
计数之前,仅过滤出包含 “SQL” 的帖子。
应用字典
为了演示这些概念,我们使用字典来存储我们的投票数据。由于字典通常保存在内存中(ssd_cache 是例外),用户应意识到数据的大小。确认我们的 votes
表的大小:
数据将以未压缩的形式存储在我们的字典中,因此如果我们要在字典中存储所有列,内存至少需要 4GB(实际上我们不会存储所有列)。字典将在我们的集群中进行复制,因此每个节点需要保留这种内存量。
在下面的示例中,我们的字典数据来自 ClickHouse 表。虽然这代表了字典的最常见来源,但支持 多种来源,包括文件、http 和数据库,包括 Postgres。正如我们所示,字典可以自动刷新,提供一种确保频繁更改的小数据集可用于直接连接的理想方法。
我们的字典需要一个主键,以便执行查找。这在概念上与事务性数据库的主键是相同的,并且应该是唯一的。我们上述查询需要在连接键上查找 - PostId
。字典应相应地填充来自我们 votes
表的每个 PostId
的支持和反对票总数。以下是获取此字典数据的查询:
要创建我们的字典需要以下 DDL - 注意我们上述查询的使用:
在自管理的 OSS 中,上述命令需要在所有节点上执行。在 ClickHouse Cloud 中,字典将自动复制到所有节点。上述操作在具有 64GB RAM 的 ClickHouse Cloud 节点上执行,耗时 36 秒进行加载。
要确认我们的字典消耗的内存:
现在,可以使用简单的 dictGet
函数检索特定 PostId
的支持和反对票。以下是我们检索 11227902
帖子的值:
这个查询不仅简单得多,而且还快了两倍以上!我们可以进一步优化,只将超过 10 个支持和反对票的帖子加载到字典中,并只存储预计算的争议值。
查询时间丰富
字典可以用于查询时查找值。这些值可以在结果中返回或用于聚合。假设我们创建一个字典,用于将用户 ID 映射到他们的位置:
我们可以使用这个字典来丰富帖子结果:
与我们上述的连接示例类似,我们可以使用同一个字典有效确定大多数帖子来自何处:
索引时间丰富
在上面的示例中,我们在查询时间使用字典来移除连接。字典也可以在插入时用于丰富行。这通常适用于补充值没有变化且来自外部源,可以用于填充字典的情况。在这种情况下,在插入时丰富行可以避免查询时间查找字典。
假设 Stack Overflow 中用户的 Location
永远不会改变(实际上他们会改变) - 特别是 users
表中的 Location
列。假设我们希望按位置对帖子表进行分析查询。此表包含一个 UserId
。
字典提供从用户 ID 到位置的映射,后面支撑着 users
表:
我们省略
Id < 0
的用户,使我们能够使用Hashed
字典类型。具有Id < 0
的用户是系统用户。
为了在帖子表的插入时间利用该字典,我们需要修改架构:
在上述示例中,Location
被声明为 MATERIALIZED
列。这意味着该值可以作为 INSERT
查询的一部分提供,并将始终被计算。
ClickHouse 还支持
DEFAULT
列(如果未提供,可以插入或计算_value)。
要填充表,我们可以使用通常的 INSERT INTO SELECT
从 S3:
我们现在可以获得来自大多数帖子的位置名称:
高级字典主题
选择字典 LAYOUT
LAYOUT
子句控制字典的内部数据结构。有多种选项,文档记录 在这里。选择正确布局的一些提示可以在 这里 找到。
刷新字典
我们为字典指定了一个 LIFETIME
值为 MIN 600 MAX 900
。LIFETIME 是字典的更新间隔,此处的值使字典在 600 到 900 秒之间的随机间隔周期性重新加载。这个随机间隔是必要的,以便在大量服务器上更新时分散字典源的负载。在更新过程中,可以继续查询字典的旧版本,只有初始加载会阻塞查询。请注意,设置 (LIFETIME(0))
会阻止字典更新。
可以使用 SYSTEM RELOAD DICTIONARY
命令强制重新加载字典。
对于 ClickHouse 和 Postgres 等数据库源,可以设置一个查询,该查询仅在字典确实发生更改时更新字典(查询的响应决定这一点),而不是在定期间隔内。进一步的细节可以在 这里找到。
其他字典类型
ClickHouse 还支持 分层、多边形 和 正则表达式 字典。