ClickHouse分布式表带WHERE过滤的DISTINCT查询失败求助
我来帮你梳理这个问题:这不是ClickHouse的设计限制,而是你使用的老旧版本中的已知Bug。
问题重现
你执行的查询语句如下:
select distinct OriginCityName from on_time.ontime_all t where t."OriginStateName" = 'California'
其中on_time.ontime_all是包含OriginCityName、OriginStateName等字段的分布式表,触发的错误为:
Received exception from server (version 1.1.54381): Code: 171. DB::Exception: Received from localhost:9000, ::1. DB::Exception: Block structure mismatch in UNION stream: different number of columns: OriginCityName String String(size = 0) OriginCityName String String(size = 0), OriginStateName String String(size = 0). 0 rows in set. Elapsed: 0.186 sec.
你发现的几种规避方式也验证了问题的触发条件:只有当DISTINCT、WHERE过滤字段不在查询列中这两个条件同时满足时,才会出现这个错误。
背后的原因
你的ClickHouse版本是1.1.54381,这是一个非常老旧的版本(早已停止官方维护)。这个Bug的核心逻辑是:
分布式表查询时,各个节点会独立执行过滤和聚合逻辑。当使用DISTINCT且查询列不包含WHERE中的过滤列时,部分节点可能因为过滤后无数据,返回的结果Block只包含查询列;而另一些节点在内部处理过程中,意外将过滤字段也带入了返回的Block中,导致Coordinator节点在合并UNION结果时,发现不同节点返回的Block结构不一致,从而抛出错误。
解决方案
结合你的业务需求,推荐以下几种方案:
- 优先升级ClickHouse版本:这个Bug在后续的稳定版本(比如19.x及以后的LTS版本)已经被完全修复。升级到最新稳定版不仅能解决这个问题,还能获得性能提升、新特性以及安全补丁,毕竟1.1.x版本已经过时很久了。
- 用PREWHERE替代WHERE:正如你发现的,PREWHERE的执行逻辑是先在每个节点上完成过滤操作,再提取需要查询的字段返回,从根源上避免了Block结构不一致的情况。
- 在查询列中加入过滤字段:虽然会多返回一列,但能保证所有节点返回的Block结构一致,之后你可以在应用层忽略该字段,示例语句:
select distinct OriginCityName, OriginStateName from on_time.ontime_all t where t."OriginStateName" = 'California'
内容的提问来源于stack exchange,提问作者fangyc

