[英]Partition pruning based on check constraint not working as expected
為什么下面的查詢計划中包含表“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
或者只是(在這里閱讀手冊) :
LOCALTIMESTAMP -- explicitly take the local time
我會考慮使用timestamptz
代替。
兩者都不能解決您的第二個問題:
2.) 回答你的問題。 約束排除不起作用。 手冊:
以下注意事項適用於約束排除:
- [...]
- 約束排除僅在查詢的
WHERE
子句包含常量(或外部提供的參數)時才有效。 例如,無法優化與非不變 function(如CURRENT_TIMESTAMP
)的比較,因為規划器無法知道 function 值可能在運行時落入哪個分區。
大膽強調我的。
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.)或者通過“偽造”一個不可變的 function:
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';
但是請注意如何使用它:雖然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
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.