基于检查约束的分区修剪未按预期工作
Partition pruning based on check constraint not working as expected
为什么table "events_201504"包含在下面的查询计划中?根据我的查询和 table 的检查约束,我希望查询规划器能够完全修剪它:
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))
您的列 created_at
是 timestamp without time zone
。
但是now()
returnstimestamp with time zone
。表达式 now() - '1 hour'::interval
被强制转换为 timestamp [without time zone]
,其中包含 两个问题:
1.)这个不是你求的,只是表达不靠谱。其结果取决于正在执行查询的会话的当前时区设置。详情在这里:
- Ignoring timezones altogether in Rails and PostgreSQL
为了表达清楚,您可以使用:
now() AT TIME ZONE 'Europe/London' -- your time zone here
或者只是 (read the manual here):
LOCALTIMESTAMP -- explicitly take the local time
我会考虑使用 timestamptz
。
都没有解决你的第二个问题:
2.) 回答你的问题。约束排除不起作用。 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.
大胆强调我的。
now()
是 CURRENT_TIMESTAMP
的 Postgres 实现。在系统目录中可以看到,只有STABLE
,没有IMMUTABLE
:
SELECT proname, provolatile FROM pg_proc WHERE proname = 'now';
proname | provolatile
--------+------------
now | s -- meaning: STABLE
解决方案
1.) 您可以通过在 WHERE
条件(始终为 "immutable")中提供常量来克服限制:
select count(*) from events
where created_at > '2015-05-25 15:49:20.037815'::timestamp; -- derived from your example
2.)或者通过"faking"一个不可变函数:
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
<b>AND created_at >= '2015-04-01 00:00:00'::timestamp
AND created_at <= '2015-04-30 23:59:59.999999'::timestamp</b>;
只需确保 now() - '1 hour'::interval
属于正确的分区,否则您显然得不到任何结果。
旁白:我宁愿在 CHECK
约束和查询中使用这个表达式。更容易处理和做同样的事情:
created_at >= '2015-04-01 0:0'::timestamp
AND created_at < '2015-05-01 0:0'::timestamp
为什么table "events_201504"包含在下面的查询计划中?根据我的查询和 table 的检查约束,我希望查询规划器能够完全修剪它:
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))
您的列 created_at
是 timestamp without time zone
。
但是now()
returnstimestamp with time zone
。表达式 now() - '1 hour'::interval
被强制转换为 timestamp [without time zone]
,其中包含 两个问题:
1.)这个不是你求的,只是表达不靠谱。其结果取决于正在执行查询的会话的当前时区设置。详情在这里:
- Ignoring timezones altogether in Rails and PostgreSQL
为了表达清楚,您可以使用:
now() AT TIME ZONE 'Europe/London' -- your time zone here
或者只是 (read the manual here):
LOCALTIMESTAMP -- explicitly take the local time
我会考虑使用 timestamptz
。
都没有解决你的第二个问题:
2.) 回答你的问题。约束排除不起作用。 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.
大胆强调我的。
now()
是 CURRENT_TIMESTAMP
的 Postgres 实现。在系统目录中可以看到,只有STABLE
,没有IMMUTABLE
:
SELECT proname, provolatile FROM pg_proc WHERE proname = 'now';
proname | provolatile
--------+------------
now | s -- meaning: STABLE
解决方案
1.) 您可以通过在 WHERE
条件(始终为 "immutable")中提供常量来克服限制:
select count(*) from events
where created_at > '2015-05-25 15:49:20.037815'::timestamp; -- derived from your example
2.)或者通过"faking"一个不可变函数:
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
<b>AND created_at >= '2015-04-01 00:00:00'::timestamp
AND created_at <= '2015-04-30 23:59:59.999999'::timestamp</b>;
只需确保 now() - '1 hour'::interval
属于正确的分区,否则您显然得不到任何结果。
旁白:我宁愿在 CHECK
约束和查询中使用这个表达式。更容易处理和做同样的事情:
created_at >= '2015-04-01 0:0'::timestamp
AND created_at < '2015-05-01 0:0'::timestamp