真实让一张500万行、每行带大payload的表做hash JOIN的右表,触发内存超限报错,验证内存开销真实只跟右表大小成正比。
代码CREATE TABLE demo.jbig (k UInt64, payload String) ENGINE=MergeTree ORDER BY k; INSERT INTO demo.jbig SELECT number, repeat('x', 500) FROM numbers(5000000); -- jbig真实约2.5GB原始数据(500万行×500字节) SELECT count() FROM demo.jleft INNER JOIN demo.jbig ON jleft.k=jbig.k SETTINGS join_algorithm='hash', max_memory_usage=100000000; -- 真实压到100MB
DB::Exception: Query memory limit exceeded: would use 331.62 MiB (attempt to allocate chunk of 259.39 MiB), maximum: 95.37 MiB: While executing FillingRightJoinSide. (MEMORY_LIMIT_EXCEEDED) -- 默认无限制情况下,这条JOIN真实用了多少内存: memory_usage: 352027750 字节(约352MB)
报错信息里的FillingRightJoinSide真实点明了失败发生的确切阶段——ClickHouse的hash join在开始真正匹配左右两表之前,要先把整个右表(本文的jbig)完整读入内存、构建成一个哈希表,这一步真实跟左表jleft有多少行、这条JOIN最终能匹配出多少行完全无关——哪怕左表只有1行,只要右表本身很大,这次JOIN依然要为右表的完整哈希表买单。
每一步都是本文上面用真实hash JOIN跑出来的原始结果。
FillingRightJoinSide 报错阶段定位