繁体   English   中英

Postgres在间隔查询中未使用部分时间戳索引(例如,now()-间隔'7 days')

[英]Postgres not using partial timestamp index on interval queries (e.g., now() - interval '7 days' )

我有一个简单的表格,用于存储在线仪表的降水读数。 这是表的定义:

    CREATE TABLE public.precip
    (
        gauge_id smallint,
        inches numeric(8, 2),
        reading_time timestamp with time zone
    )

    CREATE INDEX idx_precip3_id
        ON public.precip USING btree
        (gauge_id)

    CREATE INDEX idx_precip3_reading_time
        ON public.precip USING btree
        (reading_time)

CREATE INDEX idx_precip_last_five_days
    ON public.precip USING btree
    (reading_time)
    TABLESPACE pg_default    WHERE reading_time > '2017-02-26 00:00:00+00'::timestamp with time zone

它增长得非常大:大约有3800万条记录可以追溯到18个月。 查询很少请求超过7天的行,并且我在reading_time字段上创建了部分索引,因此Postgres可以遍历更小的索引。 但这并不是在所有查询上都使用部分索引。 确实使用了部分索引

explain analyze select * from precip where gauge_id = 208 and reading_time > '2017-02-27' 
            Bitmap Heap Scan on precip  (cost=8371.94..12864.51 rows=1169 width=16) (actual time=82.216..162.127 rows=2046 loops=1)   
            Recheck Cond: ((gauge_id = 208) AND (reading_time > '2017-02-27 00:00:00+00'::timestamp with time zone))
           ->  BitmapAnd  (cost=8371.94..8371.94 rows=1169 width=0) (actual time=82.183..82.183 rows=0 loops=1)
                ->  Bitmap Index Scan on idx_precip3_id  (cost=0.00..2235.98 rows=119922 width=0) (actual time=20.754..20.754 rows=125601 loops=1)
                      Index Cond: (gauge_id = 208)
                ->  Bitmap Index Scan on idx_precip_last_five_days  (cost=0.00..6135.13 rows=331560 width=0) (actual time=60.099..60.099 rows=520867 loops=1) 
    Total runtime: 162.631 ms

但是它在下面使用部分索引。 相反,它使用read_time的完整索引

 explain analyze select * from precip where gauge_id = 208 and reading_time > now() - interval '7 days' 

Bitmap Heap Scan on precip  (cost=8460.10..13007.47 rows=1182 width=16) (actual time=154.286..228.752 rows=2067 loops=1)
   Recheck Cond: ((gauge_id = 208) AND (reading_time > (now() - '7 days'::interval)))
      ->  BitmapAnd  (cost=8460.10..8460.10 rows=1182 width=0) (actual time=153.799..153.799 rows=0 loops=1)
              ->  Bitmap Index Scan on idx_precip3_id  (cost=0.00..2235.98 rows=119922 width=0) (actual time=15.852..15.852 rows=125601 loops=1)
                   Index Cond: (gauge_id = 208)
        ->  Bitmap Index Scan on idx_precip3_reading_time  (cost=0.00..6223.28 rows=335295 width=0) (actual time=136.162..136.162 rows=522993 loops=1)
              Index Cond: (reading_time > (now() - '7 days'::interval))
Total runtime: 228.647 ms

请注意,今天是3/5/2017,因此这两个查询本质上是在请求行。 但是似乎Postgres不会使用部分索引,除非where子句中的时间戳是“硬编码的”。 查询计划者是否不评估now() - interval '7 days'在确定要使用哪个索引之前now() - interval '7 days' 我按照第一个响应的人的建议发布了查询计划。
我写了几个函数(存储过程),总结了过去6个小时,12个小时.... 72个小时的降雨情况。 他们都在查询中使用间隔方法(例如,reading_time> now()-间隔“ 7天”)。 我不想将此代码移到应用程序中以将硬编码的时间戳发送给Postgres。 那会创建很多不必要的php代码。

关于如何鼓励Postgres使用部分索引的建议? 我的计划是每晚重新定义部分索引的日期范围(删除索引->创建索引),但是如果Postgres不使用它,那似乎有点愚蠢。

谢谢,

亚历克斯

一般而言,将索引列与常量(文字值),函数调用(至少标记为STABLE )进行比较时,可以使用索引(这意味着在单个语句中,函数的多次调用-使用相同的参数-将产生相同的结果),以及这些结果的组合。

now() (是current_timestamp的别名)被标记为STABLEtimestamp_mi_interval() (作为操作员<timestamp> - <interval>的备份函数)被标记为IMMUTABLE ,它甚至比STABLE更严格( now()current_timestamptransaction_timestamp标记transaction_timestamp的开始, statement_timestamp()标记statement_timestamp()的开始-仍然是STABLE -但是clock_timestamp()给出了在时钟上看到的时间戳,因此为VOLATILE )。

因此,从理论上讲, WHERE reading_time > now() - interval '7 days'应该能够在reading_time列上使用索引。 确实如此。 但是,由于定义了部分索引,因此计划者需要证明以下内容

但是,请记住,该谓词必须与应该从索引中受益的查询中使用的条件匹配 确切地说,仅当系统可以识别查询的WHERE条件在数学上暗示该索引的谓词时 ,才可以在查询中使用部分索引。 PostgreSQL没有完善的定理证明器,可以识别以不同形式编写的数学等效表达式。 (这样的通用定理证明者不仅很难创建,而且可能太慢而无法实际使用。) 系统可以识别简单的不等式,例如“ x <1”意味着“ x <2”; 否则,谓词条件必须与查询的WHERE条件的一部分完全匹配,否则索引将不会被识别为可用。 匹配发生在查询计划时,而不是运行时。

这就是您的查询所发生的事情,它具有and reading_time > now() - interval '7 days' now() - interval '7 days'的时间now() - interval '7 days'评估now() - interval '7 days' ,该计划已经完成。 并且PostgreSQL无法证明谓词( reading_time > '2017-02-26 00:00:00+00' )是true 但是当您使用reading_time > '2017-02-27' ,可以证明这一点。

您可以使用恒定值“指导”计划者 ,如下所示:

where gauge_id = 208
and   reading_time > '2017-02-26 00:00:00+00'
and   reading_time > now() - interval '7 days'

通过这种方式,计划者可以使用部分索引,因为indexed_col > index_conditionindexed_col > something_else意味着indexed_col将大于(至少) index_condition 也许它也会比something_else大,但是使用索引并不重要。

我不确定这是否是您想要的答案。 恕我直言,如果您有大量数据(和PostgreSQL 9.5+),则单个BRIN索引可能会更适合您的需求。

计划查询,然后将其缓存以备将来使用,包括选择要应用的索引。 由于您的查询包含volatile函数now() ,因此无法使用部分索引,因为计划者无法确定volatile函数将返回什么以及是否与部分索引匹配。 任何阅读该查询的人都会理解部分索引将是一个匹配项,但是计划者并不聪明,因为它知道now()做什么。 它唯一知道的是它是一个易失函数。

在您的情况下,更好的解决方案是根据reading_time 将表划分为较小的块。 经过适当设计的查询将仅访问单个分区。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM