我们有一个每月大约有十亿条记录的表格。考虑到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状态将QUERY限制为PARTITIONS的约束。有没有办法强制执行此操作?(例如在ORACLE中,可以向DB引擎提供有关如何使用的建议)执行一些查询,即使我也不知道是否为分区执行此操作
手动执行每个分区(给出间隔)的开销会给我们带来最糟糕的结果(而这样做我们实际上可以在没有分区的情况下工作,最好有不同的表)。
还有其他建议吗?
答案 0 :(得分: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);
答案 1 :(得分:0)
PostgreSQL的分区是一个黑客攻击,这是显示的领域之一。没有“智能分区扫描”节点类型可以锁定所有分区,但只能按顺序扫描它们,直到满足行数要求为止。
仅对constraint_exclusion
进行分区扫描限制,这要求查询规划器能够从查询中的常量证明不需要分区。
正确解决您的问题需要在PostgreSQL中添加一个新的扫描类型,其中Pg在查询开始时锁定所有分区,但只扫描它们直到它满足外部计划节点的行数要求。 / p>
您已经找到了可用的更好的解决方法之一,添加了一个常量来限制扫描哪些分区。虽然您可以自己编写明确的计划,但没有查询提示来限制扫描的分区,例如:
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
,如果规划人员没有将该条件推向自我。