下面的查询扫描100 mb的数据。
select * from table where column1 = 'val' and partition_id = '20190309';
但是下面的查询扫描了15 GB的数据(有超过90个分区)
select * from table where column1 = 'val' and partition_id in (select max(partition_id) from table);
如何优化第二个查询以扫描与第一个查询相同数量的数据?
答案 0 :(得分:4)
这里有两个问题。 select max(partition_id) from table
上方标量子查询的效率以及一个@PiotrFindeisen指出了动态过滤。
第一个问题是,对Hive表的分区键的查询要比看起来复杂得多。大多数人会认为,如果您想要分区键的最大值,则可以简单地对分区键执行查询,但这是行不通的,因为Hive允许分区为空(并且还允许非空文件不包含任何行)。具体来说,select max(partition_id) from table
上方的标量子查询要求Presto查找包含至少一行的最大分区。理想的解决方案是在Hive中具有完美的统计信息,但引擎还需要具有用于Hive的自定义逻辑,以打开分区文件,直到找到一个非空的分区为止。
如果您确定仓库中不包含空分区(或者您可以接受其中的含义),则可以在隐藏的$partitions
表上方用一个替换标量子查询。” >
select *
from table
where column1 = 'val' and
partition_id = (select max(partition_id) from "table$partitions");
第二个问题是@PiotrFindeisen指出的问题,它与计划查询执行的方式有关。大多数人会看上面的查询,发现引擎在计划期间显然应该弄清楚select max(partition_id) from "table$partitions"
的值,将其内联到计划中,然后继续进行优化。不幸的是,这通常是一个相当复杂的决定,因此引擎将其简单地建模为广播联接,其中执行的一部分会找出该值,并将该值广播给其他工人。问题在于执行的其余部分无法将此新信息添加到现有处理中,因此它仅扫描所有数据,然后滤除您要跳过的值。有一个项目正在添加此dynamic filtering,但尚未完成。
这意味着您今天可以做的最好的事情是运行两个单独的查询:一个查询获取最大partition_id,第二个查询带有内联值。
顺便说一句,在Presto 0.199中添加了隐藏的“ $ partitions”表,我们修复了0.201中的一些小错误。我不确定Athena所基于的版本,但我认为它已经过时了(我写此答案时的当前版本是309。
答案 1 :(得分:2)
我不知道它是否仍然有用,只是发现了:
代替:
select * from table where column1 = 'val' and partition_id in (select max(partition_id) from table);
使用:
select a.* from table a
inner join (select max(partition_id) max_id from table) b on a.partition_id=b.max_id
where column1 = 'val';
我认为这与对使用分区的联接的优化有关。
答案 2 :(得分:1)
编辑:Presto删除了其0.193 release中的__internal_partitions__
表,因此我建议在任何产品中均不使用下面Slow aggregation queries for partition keys
部分中定义的解决方案由于雅典娜“透明地”更新了presto版本,因此系统开始运行。最后,我只进行了简单的SELECT max(partition_date) ...
查询,还使用了Lack of Dynamic Filtering
部分中概述的回溯技巧。它比使用__internal_partitions__
表慢3倍,但至少在Athena决定更新其presto版本时不会中断。
因此,当您只需要回顾几个分区的数据价值以最大程度地匹配数据时,我想出了一种相当巧妙的方法来对大型数据集上的基于日期的分区完成此任务, ,请注意,我不确定100%information_schema.__internal_partitions__
表的使用有多脆弱。
如上所述,@ Dain实际上有两个问题。第一个是max(partition_date)查询聚合的速度如何,第二个是Presto缺乏对动态过滤的支持。
为解决第一个问题,我使用了information_schema.__internal_partitions__
表,该表使我可以在表的分区上进行快速聚合,而无需扫描文件内部的数据。 (请注意,以下查询中的partition_value
,partition_key
和partition_number
都是__internal_partitions__
表的所有列名,与表的列无关)
如果您的表只有一个分区键,则可以执行以下操作:
SELECT max(partition_value) FROM information_schema.__internal_partitions__
WHERE table_schema = 'DATABASE_NAME' AND table_name = 'TABLE_NAME'
但是,如果您有多个分区键,则将需要更多类似的东西:
SELECT max(partition_date) as latest_partition_date from (
SELECT max(case when partition_key = 'partition_date' then partition_value end) as partition_date, max(case when partition_key = 'another_partition_key' then partition_value end) as another_partition_key
FROM information_schema.__internal_partitions__
WHERE table_schema = 'DATABASE_NAME' AND table_name = 'TABLE_NAME'
GROUP BY partition_number
)
WHERE
-- ... Filter down by values for e.g. another_partition_key
)
这些查询应该相当快地运行(我的运行大约需要1-2秒),而无需扫描文件中的实际数据,但是同样,我不确定使用这种方法是否有任何麻烦。
对于我的特定用例,我能够减轻第二个问题的最坏影响,因为我希望从当前日期开始在有限的时间内始终存在一个分区(例如,我可以保证任何数据,生产或分区加载问题将在3天内得到解决)。事实证明,雅典娜在使用presto的datetime functions时会进行一些预处理,因此与使用子查询相比,动态筛选不会出现相同类型的问题。
因此,您可以使用datetime函数更改查询以限制其回溯实际最大值的距离,从而限制扫描的数据量。
SELECT * FROM "DATABASE_NAME"."TABLE_NAME"
WHERE partition_date >= cast(date '2019-06-25' - interval '3' day as varchar) -- Will only scan partitions from 3 days before '2019-06-25'
AND partition_date = (
-- Insert the partition aggregation query from above here
)