3

我们有一个表,每月大约有 10 亿条记录。考虑到 18 个月的历史,我们谈论的是 180 亿条记录。

该表每周按日期分区(因此我们有大约 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 状态。有没有办法强制执行此操作?(例如,在 ORACLE 中,可以向数据库引擎提出如何执行一些查询,即使我也不知道是否对分区执行此操作)

手动执行每个分区(给出间隔)的开销给我们带来了最差的结果(并且这样做我们实际上可以在没有分区的情况下工作,最好有不同的表)。

还有其他建议吗?

4

2 回答 2

1

此功能将一次动态查询一周,希望能利用分区的优势。SQL小提琴

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);
于 2013-04-18T15:48:22.690 回答
0

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

分区扫描限制仅使用 完成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如果规划器没有将该条件推到其自身中。

于 2013-04-18T01:37:14.693 回答