3

我们有一张每月有大约10亿条记录的表。考虑到18个月的历史,我们正在谈论180亿条记录。按日期优化的PostgreSQL分区查询

该表按日期分区(所以我们有大约74个分区)。

对于我们的一个查询,我们需要获取一个给定单位的最后1000条记录。这样

SELECT code, obs_time 
    FROM unit_position 
    WHERE unit_id = 1 
ORDER BY obs_time DESC LIMIT 1000; 

问题的东西是对于这一点,我们在下面的结果说明:

限制(成本= 96181.06..96181.09行= 10宽度= 12)

- >排序(成本= 96181.06..102157.96行= 2390760宽度= 12)

Sort Key: unit_position .obs_time 

-> Result (cost=0.00..44517.60 rows=2390760 width=12) 
    -> Append (cost=0.00..44517.60 rows=2390760 width=12) 

    -> Seq Scan on unit_position (cost=0.00..42336.00 rows=2273600 width=12) 

    -> Seq Scan on unit_position_week350 unit_position (cost=0.00..21.60 rows=1160 width=12) 

    -> ... (ALL OTHER PARTITIONS) ... 

    -> Seq Scan on unit_position_week450 unit_position (cost=0.00..21.60 rows=1160 width=12) 

在其他公顷第二,如果我们得到这样一个查询(限制查询到第一区间,我们可以得到1000条记录),我们可以>快2倍的结果:

SELECT fake, obs_time 
    FROM unit_position 
    WHERE unit_id = 1 
    AND obs_time >= NOW() - '7 weeks'::interval 
ORDER BY obs_time DESC LIMIT 1000; 

问题是,考虑到我们是通过obs_time订购,有没有办法让查询使用分区,只搜索需要的前n个分区?

在大多数情况下,结果将在最近的4个分区中(所以它只会搜索这4个分区),并且只有在极少数情况下才需要搜索所有分区。

如果在获得n个分区(按顺序)后发现1000个结果,它将不考虑剩余的分区(数十亿条记录被丢弃)。测试/解释显示PostgreSQL没有这样做。它实际上适用于所有分区(如果它没有得到WHERE状态,则将QUERY限制在PARTITIONS的约束范围内。是否有办法强制这样做?(例如在ORACLE中,可以向DB引擎提供关于如何执行一些查询,即使我也不知道是否对分区执行此操作)

手动执行每个分区的开销(给出间隔)会给我们带来最坏的结果(并且这样做我们实际上可能正在工作不分区,这将有更好有不同的表)。

任何其他建议?

回答

1

这个函数会动态查询一个星期,一次达到极限,希望利用分区。 SQL Fiddle

create or replace function unit_position_limited_by(l integer) 
returns setof unit_position 
language plpgsql as $function$ 

declare 
    week timestamp := date_trunc('week', transaction_timestamp()); 
    total integer := 0; 
    inserted integer; 
    not_exists boolean; 
begin 
    loop 
     return query execute $$ 
      select * 
      from unit_position 
      where 
       unit_id = 1 
       and obs_time >= $1 and obs_time < $2 
      order by obs_time desc 
      limit $3 
     $$ using week, week + interval '1 week', l - total; 
     get diagnostics inserted := row_count; 
     total := total + inserted; 
     exit when total = l; 
     if inserted = 0 then 
      execute $$ 
       select not exists (
        select 1 
        from unit_position 
        where obs_time < $1 
        ) 
      $$ into not_exists using week; 
      exit when not_exists; 
     end if; 
     week := week - interval '1 week'; 
    end loop; 
end; $function$; 

从它选择:

select * 
from unit_position_limited_by(1000); 
0

PostgreSQL的划分是一个黑客位的,这是显示领域之一。” s没有“智能分区扫描”节点类型,可以锁定所有分区,但只能按顺序扫描它们,直到满足行数要求。

分区扫描限制只与constraint_exclusion完成,这要求查询规划器能够从查询中的常量证明不需要分区。

如果问题得到解决,需要在PostgreSQL中添加一种新的扫描类型,其中Pg在查询开始时锁定了所有分区,但只扫描它们,直到它满足外部计划节点的行计数要求。

您已经找到了一个更好的解决方法,您可以添加一个常量来限制扫描哪些分区。没有查询提示限制扫描的分区,但你可以自己写一个明确的计划,如:

SELECT code, obs_time 
    FROM (
     SELECT * FROM unit_position_week_350 
     UNION ALL 
     SELECT * FROM unit_position_week_349 
     UNION ALL 
     SELECT * FROM unit_position_week_348 
     UNION ALL 
     SELECT * FROM unit_position_week_347 
     UNION ALL 
     SELECT * FROM unit_position_week_346 
     UNION ALL 
     SELECT * FROM unit_position_week_345 
    ) unit_position_350_to_345 
    WHERE unit_id = 1 
ORDER BY obs_time DESC LIMIT 1000; 

...但我没有测试,看看如何进行计划以及是否执行体面。如果规划人员没有按照自己的要求推送该条件,则可能需要将ORDER BY移动到子查询中,或者甚至将unit_id

+0

在我的文字(问题)我已经谈论这个案件结束。这是我已经尝试的事情之一,我实际上得到最差的结果(不是最糟糕的,但最糟糕的)。 在你的答案中,你必须做完整的查询,因为你在主查询中有ORDER BY。就像你说的,你需要通过子查询来获得它,否则你会得到最糟糕的结果。 – RGPT

+0

@RGPT不幸的是,你并没有更好的选择。正如我所说的,分区在Pg中有点原始,它有一些缺点和上升。 –