我得到了一个非常简单的查询,当在同一硬件中运行Spark SQL和Presto(3小时vs 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
的正确语义。
然而,左反联接谓词的OR
导致Left AntiJoin
唯一可行的物理联接策略是BroadcastNestedLoopJoin
。对于当前阶段,我可以将NOT IN重写为NOT EXISTS来解决这个问题。在NOT EXISTS的查询计划中,我可以看到连接谓词是Left AntiJoin(test1=test2)
,这为NOT EXISTS带来了更好的物理连接运算符(5分钟后完成(。
到目前为止,我很幸运,因为我的数据集目前没有任何NULL
属性,但将来可能会有,not in的语义正是我真正想要的。
所以我检查了Presto的查询计划,它并没有真正提供Left AntiJoin
,但它使用了带有FilterPredicate = not (expr)
的SemiJoin
。Presto的查询计划没有像Spark那样提供太多信息。
所以我的问题更像是:
我可以假设Presto有一个更好的物理连接操作符来处理NOT IN
操作吗?与Spark SQL不同,它不依赖于连接谓词isnull(op1 = op2)
的重写来确保逻辑计划级别中Not IN的正确语义。
我实际上是在Presto中对半联接(IN
谓词(执行NULL
处理的人。
Presto使用";复制null和任何行";复制模式,加上散列分区,这使它能够在IN
两侧存在NULL
的情况下正确处理IN
,而不会倒退到广播,也不会使执行成为单线程或单节点。运行时性能成本实际上与根本不存在NULL
值的情况相同。
如果你想了解更多关于Presto内部的信息,请加入Trino Community Slack上的#dev
频道。
准确地说,半联接是散列分区或广播的,这取决于基于成本的决策或配置。