Impala关于ValueTransferGraph一段代码的疑问解答

Impala关于ValueTransferGraph一段代码的疑问解答

最近在Review IMPALA-9162patch 的时候,发现ValueTransferGraph有一处代码不是很显然:

  /**
   * Add value-transfer edges to 'g' based on the registered equi-join conjuncts.
   */
  private void constructValueTransfersFromEqPredicates(WritableGraph g) {
    for (ExprId id : globalState_.conjuncts.keySet()) {
      Expr e = globalState_.conjuncts.get(id);
      Pair<SlotId, SlotId> slotIds = BinaryPredicate.getEqSlots(e);
      if (slotIds == null) continue;

      TableRef sjTblRef = globalState_.sjClauseByConjunct.get(id);
      Preconditions.checkState(sjTblRef == null || sjTblRef.getJoinOp().isSemiJoin());
      boolean isAntiJoin = sjTblRef != null && sjTblRef.getJoinOp().isAntiJoin();

      TableRef ojTblRef = globalState_.ojClauseByConjunct.get(id);
      Preconditions.checkState(ojTblRef == null || ojTblRef.getJoinOp().isOuterJoin());
      if (ojTblRef == null && !isAntiJoin) {
        // this eq predicate doesn't involve any outer or anti join, ie, it is true for
        // each result row;
        // value transfer is not legal if the receiving slot is in an enclosed
        // scope of the source slot and the receiving slot's block has a limit
        Analyzer firstBlock = globalState_.blockBySlot.get(slotIds.first);
        Analyzer secondBlock = globalState_.blockBySlot.get(slotIds.second);
        if (LOG.isTraceEnabled()) {
          LOG.trace("Considering value transfer between " + slotIds.first.toString() +
              " and " + slotIds.second.toString());
        }
        if (!(secondBlock.hasLimitOffsetClause_ &&
            secondBlock.ancestors_.contains(firstBlock))) {
          g.addEdge(slotIds.first.asInt(), slotIds.second.asInt());
          if (LOG.isTraceEnabled()) {
            LOG.trace("value transfer: from " + slotIds.first.toString() + " to " +
                slotIds.second.toString());
          }
        }
        if (!(firstBlock.hasLimitOffsetClause_ &&
            firstBlock.ancestors_.contains(secondBlock))) {
          g.addEdge(slotIds.second.asInt(), slotIds.first.asInt());
          if (LOG.isTraceEnabled()) {
            LOG.trace("value transfer: from " + slotIds.second.toString() + " to " +
                    slotIds.first.toString());
          }
        }
        continue;
      }

这里给 valueTransferGraph 添加边之前的 if 判断不是很显然。这个函数用来构建 Analyzer 中的 valueTransferGraph,这是一个有向图,图中的点是 slot,图中的边是 slot 间的值传递关系。如果从 slotA 到 slotB 有一条边,则表示 slotA 的所有约束都可以作用在 slotB 上。

比如这样一个查询:

select id, name from (
    select id, name from customer
  ) t
where id = name

这个查询有两个tuple descriptor,分别代表读取 customer 表的结果和 InlineView t 的输出结果。它们都有两个slot,代表各自的两个字段。Analyzer 会为这个查询生成如下的 valueTransferGraph,图中4个节点代表4个slot,t.id 和 customer.id 都有边指向对方,为画图方便下面画成了双向的边。同理 t.name 和 customer.name 也有边指向对方。

t.id <--> customer.id
t.name <--> customer.name

有了这个图后,再结合 t.id = t.name 这个谓词,就可以推导出 customer.id = customer.name,表现出来就是做了谓词下推。

代码中的判断是说,如果 slotB 所在的 query block 里有 limit 或 offset 语句块,并且 slotB 是在 slotA 查询块里的子查询出现,则不能添加 slotA -> slotB 这条边。为什么 limit 或 offset 会影响谓词下推呢?比如以下查询就可以把外层查询的where条件下推到子查询里:

select id, name, sum(account) from (
  select id, name, account from orders
  limit 100) t
where name = "Alice"
group by id, name

这个查询本身的结果并不固定,因为子查询是从 orders 表抽 100 行数据,如果该表的数据较大,是多个 impalad 在做读取的话,一般是执行较快的 impalad 返回的 100 行被采用。

然而,我们把谓词 name = “Alice” 下推到子查询里也没有错:

select id, name, sum(account) from (
  select id, name, account from orders
  where name = "Alice"
  limit 100) t
where name = "Alice"
group by id, name

只不过是子查询返回的结果更“精确”了,都不会被外层查询的谓词过滤掉。

有什么例子是下推外层循环的谓词后会出错的呢?我后来想到了这样一个例子:
假设 orders 表的 id 列是从0开始逐行连续递增的,即是 0、1、2、3、…… 这样的形式。假设 orders 表的数据量远超过200行(如 id 至少到能排到 200)考虑以下查询:

select id, name from (
  select id, name from orders
  order by id asc
  limit 100) t
where id % 2 = 0

子查询 t 返回的行对应的 id 依次是 0、1、2、3、……、99. 在外层查询里被谓词 “id % 2 = 0” 过滤后,只剩下 id 为偶数的行,也就是 50 行。

如果我们下推谓词到子查询里,变成这样:

select id, name from (
  select id, name from orders
  where id % 2 = 0
  order by id asc
  limit 100) t
where id % 2 = 0

则子查询返回的是100行偶数id的数据,这样最终的结果就是 100 行!

同样的可以举一个子查询包含 offset 语句块的反例,因此子查询里有 limit/offset 语句块时,外层查询的谓词是不一定能下推的。

在研究 IMPALA-9162 的时候,我发现如果子查询里有 outer join,且外层查询的谓词是作用在 nullable 那端的 slot 时,谓词也是不能下推的,详见我在 JIRA 里的 comment: https://issues.apache.org/jira/browse/IMPALA-9162?focusedCommentId=16988532&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-16988532

猜你喜欢

转载自blog.csdn.net/huang_quanlong/article/details/103495911
今日推荐