我得到了一个非常简单的查询,当在同一硬件上运行Spark SQL和Presto时(3小时v.s 3分钟),显示出显着的性能差异。
select field from test1
where field not in
(select field from test2)
经过对查询计划的研究,我发现原因是Spark SQL如何处理NOT IN
谓词子查询。为了正确处理NOT IN的NULL,Spark SQL将NOT IN
谓词转换为Left AntiJoin( (test1=test2) OR isNULL(test1=test2))
。
Spark SQL引入OR isNULL(test1=test2)
以确保NOT IN
的正确语义。
但是,Left AntiJoin连接谓词的OR
导致Left AntiJoin
唯一可行的物理连接策略是BroadcastNestedLoopJoin
。在当前阶段,我可以将NOT IN改写为NOT EXISTS来解决此问题。在NOT EXISTS的查询计划中,我可以看到联接谓词为Left AntiJoin(test1=test2)
,这会为NOT EXISTS(5分钟完成)带来更好的物理联接运算符。
到目前为止,我很幸运,因为我的数据集当前没有任何NULL
属性,但是将来可能会具有,并且我真正想要的是NOT IN的语义。
所以我检查了Presto的查询计划,它实际上并没有提供Left AntiJoin
,但是它使用SemiJoin
和FilterPredicate = not (expr)
。 Presto的查询计划没有提供太多信息,例如Spark。
所以我的问题更像是:
我是否可以认为Presto具有更好的物理联接运算符来处理NOT IN
操作?与Spark SQL不同,它不依赖于连接谓词isnull(op1 = op2)
的重写来确保逻辑计划级别中NOT IN的正确语义。
我实际上是在Presto中对半联接(NULL
谓词)实施IN
处理的人。
Presto除了使用散列分区¹之外,还使用“复制空值和任何行”的复制模式,这使得它可以在IN
两侧存在NULL
的情况下正确处理IN
,而不会退回到广播,或使执行单线程或单节点。运行时性能成本实际上与NULL
值根本不存在相同。
如果要了解有关Presto内部的更多信息,请在#dev
上加入Presto Community Slack通道。
确切地说,半连接是散列分区或广播的,具体取决于基于成本的决策或配置。