如何连接两个JDBC表并避免Exchange?

时间:2017-12-01 16:55:57

标签: apache-spark apache-spark-sql

我有类似ETL的场景,其中我从多个JDBC表和文件中读取数据并执行一些聚合并在源之间进行连接。

在一个步骤中,我必须加入两个JDBC表。我试过做类似的事情:

val df1 = spark.read.format("jdbc")
            .option("url", Database.DB_URL)
            .option("user", Database.DB_USER)
            .option("password", Database.DB_PASSWORD)
            .option("dbtable", tableName)
            .option("driver", Database.DB_DRIVER)
            .option("upperBound", data.upperBound)
            .option("lowerBound", data.lowerBound)
            .option("numPartitions", data.numPartitions)
            .option("partitionColumn", data.partitionColumn)
            .load();

val df2 = spark.read.format("jdbc")
            .option("url", Database.DB_URL)
            .option("user", Database.DB_USER)
            .option("password", Database.DB_PASSWORD)
            .option("dbtable", tableName)
            .option("driver", Database.DB_DRIVER)
            .option("upperBound", data2.upperBound)
            .option("lowerBound", data2.lowerBound)
            .option("numPartitions", data2.numPartitions)
            .option("partitionColumn", data2.partitionColumn)
            .load();

df1.join(df2, Seq("partition_key", "id")).show();

请注意,partitionColumn在两种情况下都是相同的 - “partition_key”。

然而,当我运行这样的查询时,我可以看到不必要的交换(为了可读性而清除计划):

df1.join(df2, Seq("partition_key", "id")).explain(extended = true);
Project [many many fields]
+- Project [partition_key#10090L, iv_id#10091L, last_update_timestamp#10114,  ... more fields]
    +- SortMergeJoin [partition_key#10090L, id#10091L], [partition_key#10172L, id#10179L], Inner
       :- *Sort [partition_key#10090L ASC NULLS FIRST, iv_id#10091L ASC NULLS FIRST], false, 0
       :  +- Exchange hashpartitioning(partition_key#10090L, iv_id#10091L, 4)
       :     +- *Scan JDBCRelation((select mod(s.id, 23) as partition_key, s.* from tab2 s)) [numPartitions=23] [partition_key#10090L,id#10091L,last_update_timestamp#10114] PushedFilters: [*IsNotNull(PARTITION_KEY)], ReadSchema: struct<partition_key:bigint,id:bigint,last_update_timestamp:timestamp>
       +- *Sort [partition_key#10172L ASC NULLS FIRST, id#10179L ASC NULLS FIRST], false, 0
          +- Exchange hashpartitioning(partition_key#10172L, iv_id#10179L, 4)
             +- *Project [partition_key#10172L, id#10179L ... 75 more fields]
               +- *Scan JDBCRelation((select mod(s.id, 23) as partition_key, s.* from tab1 s)) [numPartitions=23] [fields] PushedFilters: [*IsNotNull(ID), *IsNotNull(PARTITION_KEY)], ReadSchema: struct<partition_key:bigint,id:bigint...

如果我们已经使用numPartitions分区读取和其他选项,分区计数是相同的,为什么需要另一个Exchange?我们可以以某种方式避免这种不必要的洗牌吗?在测试数据上,我看到Sparks在此Exchange中发送了超过150M的数据,其中生产Datasets要大得多,因此它可能是严重的瓶颈。

1 个答案:

答案 0 :(得分:2)

使用Date Source API的当前实现,没有上游传递的分区信息,因此即使数据可以在没有shuffle的情况下连接,Spark也无法使用此信息。因此你的假设是:

  

JdbcRelation在读取时使用RangePartitioning

是不正确的。此外,看起来Spark使用相同的内部代码来处理基于范围的JDBC分区和基于谓词的JDBC分区。虽然前者可以翻译为SortOrder,但后者可能与Spark SQL不兼容。

如有疑问,可以使用Partitioner和内部QueryExecution检索RDD信息:

df.queryExecution.toRdd.partitioner

这可能会在将来发生变化(SPIP:​ ​ Data​ ​ Source​ ​ API​ ​ V2SPARK-15689 - Data source API v2 Spark Data Frame. PreSorded partitions )。