这篇文章是关于火花如何在使用同一表的子查询联接表时效率低下。使用Spark 2.2和Scala。
我有一个具有以下架构的表(fieldcnts):
|key |name |value |cnt |keycnt |minkeycnt|type |
|A |f1 |123 |3 |100 |30 |-1 |
|A |f1 |234 |5 |100 |30 |-1 |
|A |f2 |345 |50 |225 |75 |-1 |
|B |f1 |456 |40 |100 |30 |-1 |
...
此表派生自具有以下查询(Q1)的缓存表:
SELECT key, 'f1' AS name, f1 AS value, COUNT(*) AS cnt,
first(keycnt) AS keycnt, first(keymincnt) AS keymincnt
FROM table1
GROUP BY key, f1
UNION ALL
SELECT key, 'f2' AS name, f2 AS value, COUNT(*) AS cnt,
first(keycnt) AS keycnt, first(keymincnt) AS keymincnt
FROM table1
GROUP BY key, f2
UNION ALL
...
(simiarly for f3, f4 and f5)
表1的架构为:
|key|f1|f2|f3|f4|f5|keycnt|keymincnt|
因此,此阶段的fieldcnts表具有每个(键,f *)元组的计数。我目前在每行中都冗余存储了keycnt和keymincnt(keycnt的百分比),以避免额外的联接和计算,但是如果我获得更好的性能(具有联接和较小的数据集),以后可能会更改。
类型字段默认为-1。随后,我需要根据某些条件将此值设置为4个值之一。我使用以下查询(Q2)进行此操作:
SELECT R.key, R.name, R.value, R.cnt
CASE
WHEN ${cond1} THEN -2
WHEN ${cond2} THEN -3
WHEN ${cond3} THEN -4
ELSE -1
END AS type
FROM fieldcnts R
JOIN (SELECT key, name, COUNT(*) AS numrecs, SUM(CASE WHEN cnt >= keymincnt THEN cnt ELSE 0 END) AS fieldsum
FROM fieldcnts
GROUP BY key, name) AS S
ON R.key = S.key AND R.name = S.name
每个$ {condn}是一个涉及cnt,keymincnt,keycnt,fieldum和numrecs的条件(例如(A B)(可能很专有,以防万一!)< / p>
所以这可行,但是在调优时我注意到,由于Q2中的子查询使用与外部查询相同的表,因此它迫使子查询从磁盘上读取(镶木地板扫描)。这很麻烦,因为Q1 UNION ALL查询中的每个查询都在发生这种情况!
在计算table1时,我首先注意到了这一点。读取一组镶木地板文件,它选择字段的子集,然后过滤掉keycnt (实际上tbl在此之前就已经被很好地使用过,所以我现在就收到它的缓存了)。查看DAG,我可以清楚地看到外部表是从缓存中读取的,而内部表是从磁盘中读取的!我实际上使用Windowing解决了这个问题: 好多了。在这种情况下,不会读取SortMergeJoin或磁盘。 现在回到问题2:这与table1查询类似,但更为复杂。我还无法提出使用Windowing的方案(仍然是火花的新手)。这里有使用Windowing的方法吗?另外,有没有一种方法可以防止火花从磁盘读取使用同一表的子查询? 谢谢! 更新
回到这个问题。我的同事遇到了一个不同的解决方案:使用mapPartitions,它更适合我的问题。 我保留了此查询,该查询正是通过键分配给分区的: 然后,我在mapPartitions scala函数中编写了其余算法。更清洁,更快。SELECT R.key, R.f1, R.f2, R.f3, R.f4, R.f5, S.cnt AS keycnt, 0.33*S.cnt AS keymincnt
FROM tbl R
JOIN (SELECT key, COUNT(*) AS keycnt
FROM tbl T
GROUP BY key
HAVING keycnt >= ${minCnt}) AS S
ON R.key = S.key
val dfSQL = spark.sql("SELECT key, f1, f2, f3, f4, f5 FROM tbl")
.withColumn("keycnt", count("*").over(Window.partitionBy($"key")))
.withColumn("keymincnt", $"key"*0.33)
.filter($"keycnt" >= minCnt)
SELECT R.key, R.f1, R.f2, R.f3, R.f4, R.f5, S.cnt AS keycnt, 0.33*S.cnt AS keymincnt
FROM tbl R
JOIN (SELECT key, COUNT(*) AS keycnt
FROM tbl T
GROUP BY key
HAVING keycnt >= ${minCnt}) AS S
ON R.key = S.key