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

标签 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))

最佳答案

您的专栏created_at类型为 timestamp without time zone .

但是now()返回 timestamp with time zone .表达式 now() - '1 hour'::interval被强制为 timestamp [without time zone] ,它带有两个问题:

1.) 你没有要求这个,但表达不可靠。其结果取决于正在执行查询的 session 的当前时区设置。详情如下:

为了表达清楚,你可以使用:

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

或者只是 (read the manual here) :

LOCALTIMESTAMP  -- explicitly take the local time

我会考虑使用 timestamptz相反。
都不能解决你的第二个问题:

2.) 回答您的问题。约束排除不起作用。 The manual:

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 中提供常量来克服限制条件(始终是“不可变的”):

SELECT count(*) FROM events
WHERE created_at > '2015-05-25 15:49:20.037815'::timestamp;  -- from your example

2.) 或者通过“伪造”一个不可变函数:

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

然后:

SELECT count(*) FROM events
WHERE created_at > f_now_immutable() - interval '1 hour';

但是请注意如何使用它:while 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;

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'::timestamp
AND    created_at <  '2015-05-01'::timestamp

关于sql - 基于检查约束的分区修剪未按预期工作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30446526/

相关文章:

SQL - 将 2 个外键左连接到 1 个主键

json - PostgreSQL 中#> 和->> 运算符有什么区别?

node.js - 实时数据库消息传递

postgresql - 在 Symfony2 和 PostgreSQL 中调用存储过程?

sql - HAVING 子句有什么问题?

postgresql - 插入后同步两个表

sql - postgres根据条件执行存储过程

mysql - SQL - 删除字段中的重复项

mysql - 合并 MySQL 中同一时间戳上具有空值的行

SQL Server DATEDIFF 准确性