我的应用程序向白水划船者报告降雨和流量信息。Postgres 是我的数据存储,用于以 15 分钟为间隔的仪表读数。随着时间的推移,这些表变得相当大,并且 Postgres 10 中范围分区的可用性激励我离开我的共享托管服务并在 Linode 从头开始构建服务器。在我将读数分成 2 周的块后,我对这些大表的查询变得更快。几个月后,我检查了查询计划,非常惊讶地发现在查询中使用 now() 会导致 PG 扫描我分区表上的所有索引。有没有搞错?!?!分区数据的目的不是为了避免这种情况吗?
这是我的设置:我的分区表
CREATE TABLE public.precip
(
gauge_id smallint,
inches numeric(8, 2),
reading_time timestamp with time zone
) PARTITION BY RANGE (reading_time)
我每两周创建一次分区,所以到目前为止我有大约 50 个分区表。我的分区之一:
CREATE TABLE public.precip_y2017w48 PARTITION OF public.precip
FOR VALUES FROM ('2017-12-03 00:00:00-05') TO ('2017-12-17 00:00:00-05');
然后每个分区在 gauge_id 和 reading_time 上建立索引
我有很多疑问,比如
WHERE gauge_id = xxx
AND precip.reading_time > (now() - '01:00:00'::interval)
AND precip.reading_time < now()
正如我所提到的,postgres 会为每个“子”表扫描 reading_time 上的所有索引,而不是仅查询在查询范围内具有时间戳的子表。如果我输入文字值(例如 precip.reading_time > '2018-03-01 01:23:00')而不是 now(),它只会扫描相应子表的索引。我已经阅读了一些内容,并且我知道 now() 是易变的,并且规划者不会知道查询执行时的值是什么。我还读到查询计划很昂贵,因此 postgres 缓存计划。我可以理解为什么 PG 被编程来做到这一点。但是,我读到的一个反驳论点是,重新计划的查询可能比最终忽略分区的查询便宜得多。我同意——在我的情况下可能就是这种情况。
作为一种变通方法,我创建了这个函数:
CREATE OR REPLACE FUNCTION public.hours_ago2(i integer)
RETURNS timestamp with time zone
LANGUAGE 'plpgsql'
COST 100
IMMUTABLE
ROWS 0
AS $BODY$
DECLARE X timestamp with time zone;
BEGIN
X:= now() + cast(i || ' hours' as interval);
RETURN X;
END;
$BODY$;
注意 IMMUTABLE 语句。现在,当发出类似的查询时
select * from stream
where gauge_id = 2142 and reading_time > hours_ago2(-3)
and reading_time < hours_ago2(0)
PG 只搜索存储该时间范围内数据的分区表。这是我一开始设置分区时的目标。嘘。但这安全吗?查询计划器是否会缓存 hours_ago2(-3) 的结果并在接下来的几个小时内一遍又一遍地使用它?缓存几分钟就好了。同样,我的应用程序报告降雨和流量信息;它不处理金融交易或任何其他“关键”类型的数据处理。我已经测试过像 select hours_ago2(-3) 这样的简单语句,它每次都会返回新值。所以看起来很安全。但真的是这样吗?