Partition pruning based on check constraint not working as expected

橙三吉。 提交于 2019-12-22 01:14:38

问题


Why is the table "events_201504" included in the query plan below? Based on my query and the check constraint on that table I would expect the query planner to be able to prune it entirely:

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

Time and configuration:

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

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

The query plan:

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:


Your column created_at is timestamp without time zone.

But now() returns timestamp with time zone. The expression now() - '1 hour'::interval is being coerced to timestamp [without time zone], which carries two problems:

1.) You did not ask for this one, but the expression is unreliable. Its result depends on the current time zone setting of the session the query is being executed in. Details here:

  • Ignoring timezones altogether in Rails and PostgreSQL

To make the expression clear, you could use:

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

Or just (read the manual here):

LOCALTIMESTAMP  -- explicitly take the local time

I would consider working with timestamptz instead.
Neither solves your second problem:

2.) Answer to your question. Constraint exclusion does not work. Per documentation:

The following caveats apply to constraint exclusion:

Constraint exclusion only works when the query's WHERE clause contains constants (or externally supplied parameters). For example, a comparison against a non-immutable function such as CURRENT_TIMESTAMP cannot be optimized, since the planner cannot know which partition the function value might fall into at run time.

Bold emphasis mine.

now() is the Postgres implementation of CURRENT_TIMESTAMP. As you can see in the system catalog, it is only STABLE, not IMMUTABLE:

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

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

Solutions

1.) You can overcome the limitation by providing a constant in the WHERE condition (which is always "immutable"):

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

2.) Or by "faking" an immutable function:

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

And then:

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

Be careful how you use this though: while now() is STABLE (does not change for the duration of a transaction), it does change between transactions, so take care not to use this in prepared statements (except as parameter value) or indexes or anything where it might bite you.

3.) Or you can add seemingly redundant constant WHERE clauses to your current query that match the constraint on your partition:

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;

Just make sure yourself that now() - '1 hour'::interval falls into the right partition or you get no results, obviously.

Aside: I would rather use this expression in CHECK constraints and query. Easier to handle and does the same:

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


来源:https://stackoverflow.com/questions/30446526/partition-pruning-based-on-check-constraint-not-working-as-expected

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!