基于检查约束的分区修剪未按预期工作

时间:2015-05-25 21:56:21

标签: sql postgresql postgresql-9.3 database-partitioning postgresql-performance

为什么表" events_201504"包含在下面的查询计划中?基于我的查询和该表上的检查约束,我希望查询规划器能够完全修剪它:

database=# \d events_201504
                                   Table "public.events_201504"
    Column     |            Type             |                           Modifiers
---------------+-----------------------------+---------------------------------------------------------------
 id            | bigint                      | not null default nextval('events_id_seq'::regclass)
 created_at    | timestamp without time zone |
Indexes:
    "events_201504_pkey" PRIMARY KEY, btree (id)
    "events_201504_created_at" btree (created_at)
Check constraints:
    "events_201504_created_at_check" CHECK (created_at >= '2015-04-01 00:00:00'::timestamp without time zone AND created_at <= '2015-04-30 23:59:59.999999'::timestamp without time zone)
Inherits: events

时间和配置:

database=# select now();
              now
-------------------------------
 2015-05-25 16:49:20.037815-05

database=# show constraint_exclusion;
 constraint_exclusion
----------------------
 on

查询计划:

database=# explain select count(1) from events where created_at > now() - '1 hour'::interval;
                                                                QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=3479.86..3479.87 rows=1 width=0)
   ->  Append  (cost=0.00..3327.90 rows=60784 width=0)
         ->  Seq Scan on events  (cost=0.00..0.00 rows=1 width=0)
               Filter: (created_at > (now() - '01:00:00'::interval))
         ->  Index Only Scan using events_201504_created_at on events_201504  (cost=0.57..4.59 rows=1 width=0)
               Index Cond: (created_at > (now() - '01:00:00'::interval))
         ->  Index Only Scan using events_201505_created_at on events_201505  (cost=0.57..3245.29 rows=60765 width=0)
               Index Cond: (created_at > (now() - '01:00:00'::interval))

1 个答案:

答案 0 :(得分:2)

您的专栏created_attimestamp without time zone

now()会返回timestamp with time zone。表达式now() - '1 hour'::interval正被强制转移到timestamp [without time zone],其中包含两个问题

1。)你没有要求这个,但表达方式不可靠。其结果取决于正在执行查询的会话的当前时区设置。详细信息:

要使表达式清晰,您可以使用:

now() AT TIME ZONE 'Europe/London' -- your time zone here

或只是(read the manual here)

LOCALTIMESTAMP  -- explicitly take the local time

我会考虑改为使用timestamptz 两者都没有解决你的第二个问题:

2。)回答您的问题。约束排除不起作用。 Per documentation:

  

以下警告适用于约束排除:

     

约束排除仅在查询的WHERE子句包含常量(或外部提供的参数)时有效。例如,a   比较非不可变函数,如   CURRENT_TIMESTAMP无法优化,因为规划人员无法知道   函数值的哪个分区可能会在运行时落入。

大胆强调我的。

now()CURRENT_TIMESTAMP的Postgres实现。正如您在系统目录中看到的那样,它只是STABLE,而不是IMMUTABLE

SELECT proname, provolatile FROM pg_proc WHERE proname = 'now';

proname | provolatile
--------+------------
now     | s              -- meaning: STABLE

解决方案

1。)您可以通过在WHERE条件中提供常量(始终为&#34;不可变&#34;)来克服限制:

select count(*) from events
where created_at > '2015-05-25 15:49:20.037815'::timestamp;  -- derived from your example

2。)或者通过&#34;伪造&#34;一个不可变的函数:

CREATE FUNCTION f_now_immutable()
  RETURNS timestamp AS
$func$
SELECT now() AT TIME ZONE 'UTC'  -- your time zone here
$func$  LANGUAGE sql IMMUTABLE;

然后:

select count(*) from events
where created_at > f_now_immutable() - interval '1 hour'

小心你如何使用它:虽然now()STABLE(在交易期间不会改变),但在交易之间发生更改,所以注意不要在准备好的陈述中使用它(除了作为参数值)或索引或任何可能咬你的东西。

3。)或者您可以向当前查询中添加看似冗余的常量WHERE子句,这些子句与您的分区上的约束匹配:

SELECT count(*)
FROM   events
WHERE  created_at > now() - '1 hour'::interval
AND    created_at >= '2015-04-01 00:00:00'::timestamp
AND    created_at <= '2015-04-30 23:59:59.999999'::timestamp;

请确保自己now() - '1 hour'::interval落入正确的分区,否则就没有结果。

除此之外:我宁愿在CHECK约束和查询中使用此表达式。更容易处理并做同样的事情:

       created_at >= '2015-04-01 0:0'::timestamp
AND    created_at <  '2015-05-01 0:0'::timestamp