Spark SQL / Hive查询永远加入

时间:2015-12-02 06:31:22

标签: mysql apache-spark apache-spark-sql

所以我做的事情应该很简单,但显然它不在Spark SQL中。

如果我在MySQL中运行以下查询,查询将在几分之一秒内完成:

SELECT ua.address_id
FROM user u
inner join user_address ua on ua.address_id = u.user_address_id
WHERE u.user_id = 123;

但是,在Spark(1.5.1)下的HiveContext中运行相同的查询需要超过13秒。添加更多联接会使查询运行很长时间(超过10分钟)。我不确定我在这里做错了什么,以及如何加快速度。

这些表是作为临时表加载到Hive上下文中的MySQL表。这是在单个实例中运行,数据库在远程计算机上运行。

  • 用户表有大约480万行。
  • user_address表有350,000行。

这些表具有外键字段,但在db中没有定义明确的fk关系。我正在使用InnoDB。

Spark中的执行计划:

安排:

  

扫描   JDBCRelation(JDBC:MySQL的:// 。用户,[Lorg.apache.spark.Partition; @ 596f5dfc,   {user = ,password = ,url = jdbc:mysql:// ,dbtable = user})   [ADDRESS_ID#0L,user_address_id#27L]

     

过滤器(user_id#0L = 123)扫描   JDBCRelation(JDBC:MySQL的:// .user_address,   [Lorg.apache.spark.Partition; @ 2ce558f3,{user = ,密码= ,   url = jdbc:mysql:// ,dbtable = user_address})[address_id#52L]

     

ConvertToUnsafe ConvertToUnsafe

     

TungstenExchange hashpartitioning(address_id#52L)TungstenExchange   hashpartitioning(user_address_id#27L)TungstenSort [address_id#52L   ASC],false,0 TungstenSort [user_address_id#27L ASC],false,0

     

SortMergeJoin [user_address_id#27L],[address_id#52L]

     

==物理计划== TungstenProject [address_id#0L]

2 个答案:

答案 0 :(得分:4)

首先执行的查询类型效率极低。至于现在(Spark 1.5.0 *)执行这样的连接,每次执行查询时都必须对两个表进行混洗/散列分区。如果users表中user_id = 123谓词最有可能被推下但仍需要user_address完全随机播放,则不应该出现问题。

此外,如果表只是注册而不是缓存,那么每次执行此查询都会将整个user_address表从MySQL提取到Spark。

  

我不确定我在这里做错了什么以及如何加快速度。

目前还不清楚为什么要将Spark用于应用程序,但单机设置,小数据和查询类型表明Spark不适合这里。

一般来说,如果应用程序逻辑需要单个记录访问,那么Spark SQL就不会表现良好。它专为分析查询而设计,而不是作为OLTP数据库的替代品。

如果单个表格/数据框小得多,您可以尝试广播。

import org.apache.spark.sql.DataFrame
import org.apache.spark.sql.functions.broadcast

val user: DataFrame = ???
val user_address: DataFrame = ???

val userFiltered = user.where(???)

user_addresses.join(
  broadcast(userFiltered), $"address_id" === $"user_address_id")

*这应该在Spark 1.6.0中使用SPARK-11410进行更改,这应该启用持久表分区。

答案 1 :(得分:3)

在类似的情况下我遇到了同样的问题(Spark 1.5.1,PostgreSQL 9.4)。

给出两个表格,如

val t1 = sqlContext.read.format("jdbc").options(
  Map(
    "url" -> "jdbc:postgresql:db",
    "dbtable" -> "t1")).load()

val t2 = sqlContext.read.format("jdbc").options(
  Map(
    "url" -> "jdbc:postgresql:db",
    "dbtable" -> "t2")).load()

然后在已注册的临时表上连接HQL会导致对其中一个表进行全表扫描(在我的例子中,它是孩子)。

无论如何,解决方法是将查询推送到底层RDBMS:

val joined = sqlContext.read.format("jdbc").options(
  Map(
    "url" -> "jdbc:postgresql:db",
    "dbtable" -> "(select t1.*, t2.* from t1 inner join t2 on ...) as t")).load()

这样底层RDBMS的查询优化器就会启动,在我的情况下,它会切换到索引扫描。另一方面,Spark推下了两个独立的查询,而RDBMS无法真正优化它。