bigywater.tech
ClickHouse 内部原理 · 41

字典 JOIN:为什么有时比普通 JOIN 更快

真实对比dictGet、普通JOIN、direct算法三种字典查找方式在500万行主表上的内存和耗时表现。

4.5倍内存差距
真实dictGet比普通JOIN省约4.5倍内存
省内存≠更快
真实direct算法内存最省但耗时反而最长

做的事:真实建一个Dictionary,对比三种不同的查找方式

代码CREATE TABLE demo.dictsrc (id UInt64, name String) ENGINE=MergeTree ORDER BY id; INSERT INTO demo.dictsrc SELECT number, concat('name', toString(number)) FROM numbers(1000000); CREATE DICTIONARY demo.namedict (id UInt64, name String) PRIMARY KEY id SOURCE(CLICKHOUSE(TABLE 'dictsrc' DB 'demo')) LAYOUT(HASHED()) LIFETIME(MIN 300 MAX 600); SELECT k, dictGet('demo.namedict', 'name', k) FROM demo.jleft; -- dictGet SELECT jleft.k, name FROM demo.jleft LEFT JOIN demo.dictsrc ON jleft.k=dictsrc.id; -- 普通JOIN SELECT jleft.k, name FROM demo.jleft LEFT JOIN demo.namedict ON jleft.k=namedict.id SETTINGS join_algorithm='direct'; -- direct算法

验证:真实内存差了4.5倍,但direct算法的耗时是个意外

实测 · 本机执行输出(500万行主表,100万行字典)
dictGet()                     :真实内存 27,885,000 字节(约27.9MB),耗时 36ms
普通JOIN(join_algorithm=hash) :真实内存 124,768,407 字节(约124.8MB),耗时 34ms
join_algorithm='direct'       :真实内存 22,835,592 字节(约22.8MB),耗时 63ms

dictGet和direct两种方式真实都比普通JOIN省内存(分别省约4.5倍和5.5倍)——因为Dictionary的HASHED布局在查询开始前就已经把100万行数据加载并构建成一份常驻的哈希结构,查询时不需要像普通JOIN那样每次现场为右表重新构建一份哈希表。但direct算法虽然内存最省,耗时(63ms)反而比dictGet(36ms)和普通JOIN(34ms)都要长。

诚实的取舍:省内存不是没有代价的

Dictionary真实带来的核心优势是"预先加载、常驻内存、多个查询共享同一份数据结构"——本文这次单条查询的对比里,dictGet和普通JOIN构建哈希表的动作某种程度上是可比的(后台字典数据本来就常驻),但direct算法多花的这部分时间,真实来自它的实现方式:direct是把Dictionary当成一种特殊的、支持按主键直接查找(不需要构建临时哈希表,本身自带索引)的JOIN右表来处理,但这种查找方式在本文的测试里,单次调用开销比dictGet的批量向量化调用要略高,导致500万次查找累积下来耗时反而更长。真实的建议是:如果同一份维度数据会被多个不同查询反复用到,用Dictionary(不管是dictGet还是direct JOIN写法)几乎总是比每次现查一遍普通表做JOIN更省资源,因为字典只需要加载一次、被所有查询共享;但具体在dictGet和direct两种写法之间怎么选,本文的真实测试说明不能想当然认为"更省内存=更快",值得针对自己的真实查询模式跑一次测试再决定。

分步演示:三种查找方式,真实各自的取舍

每一步都是本文上面用真实Dictionary跑出来的原始结果。

字典查找真实省内存,但不是每种写法都更快
这一步新验证的方式

    本文用到的工具

    • 本机 ClickHouse 26.9.4(单节点),真实的 Dictionary(HASHED())、dictGet()、join_algorithm='direct'
    京ICP备2026067502号-1京公网安备11010502063487号