问题
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 asCURRENT_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