one*_*vat 5 postgresql constraint partitioning timestamp check-constraints
该表reports
按天分区表,例如reports_20170414
,reports_20170415
约束 SQL 定义如下
CHECK (
rpt_datetime >= '2017-04-14 00:00:00+00'::timestamp with time zone
AND
rpt_datetime < '2017-04-15 00:00:00+00'::timestamp with time zone
)
Run Code Online (Sandbox Code Playgroud)
让我们考虑两种类型的查询
SELECT SUM(rpt_unique_clicks)
FROM reports WHERE rpt_datetime >= '2017-04-14 00:00:00';
Run Code Online (Sandbox Code Playgroud)
以上查询在亚秒内运行,一切正常。
SELECT SUM(rpt_unique_clicks)
FROM reports WHERE rpt_datetime >=
date_trunc('day', current_timestamp);
Run Code Online (Sandbox Code Playgroud)
相反,上面的查询运行至少 15 秒。
SELECT date_trunc('day', CURRENT_TIMESTAMP), '2017-04-14 00:00:00';
Run Code Online (Sandbox Code Playgroud)
返回
2017-04-14 00:00:00 +00:00 | 2017-04-14 00:00:00
Run Code Online (Sandbox Code Playgroud)
当我检查为什么后者运行时间长(使用解释分析)时,我完成了它访问并扫描每个表(~500)的结果,但前者仅访问,reports_20170414
因此约束检查存在问题。
我想查询今天,而不是像后一个查询那样使用准备好的语句。为什么date_trunc('day', CURRENT_TIMESTAMP)
不等于 2017-04-14 00:00:00
?
我无法完全回答为什么date_trunc('day', CURRENT_TIMESTAMP)
不等于常量......即使两者CURRENT_TIMESTAMP
都date_trunc
定义为IMMUTABLE
。
但我认为我们可以做出一个实验性的、有根据的猜测:显然,PostgreSQL 规划器不评估函数。因此,它没有任何好的方法来知道要检查哪些分区,并制定一个检查所有分区的计划。
实验检查
我们创建一个基(父)表:
-- Base table
CREATE TABLE reports
(
rpt_datetime timestamp without time zone DEFAULT now() PRIMARY KEY,
rpt_unique_clicks integer NOT NULL DEFAULT 1,
something_else text
) ;
Run Code Online (Sandbox Code Playgroud)
我们创建一个自动分区插入触发器:
-- Auto-partition using trigger
-- Adapted from http://blog.l1x.me/post/2016/02/16/creating-partitions-automatically-in-postgresql.html
CREATE OR REPLACE FUNCTION create_partition_and_insert ()
RETURNS TRIGGER AS
$$
DECLARE
_partition_date text ;
_partition_date_p1 text ;
_partition text ;
BEGIN
_partition_date := to_char(new.rpt_datetime, 'YYYYMMDD');
_partition := 'reports_' || _partition_date ;
-- Check if table exists...
-- (oversimplistic: doesn't take schemas into account... doesn't check for possible race conditions)
if not exists (SELECT relname FROM pg_class WHERE relname=_partition) THEN
_partition_date_p1 := to_char(new.rpt_datetime + interval '1 day', 'YYYYMMDD');
RAISE NOTICE 'Creating %', _partition ;
EXECUTE 'CREATE TABLE ' || _partition ||
' (CHECK (rpt_datetime >= timestamp ''' || _partition_date || ''' AND rpt_datetime < timestamp ''' || _partition_date_p1 || '''))' ||
' INHERITS (reports)' ;
end if ;
EXECUTE 'INSERT INTO ' || _partition || ' SELECT(reports ' || quote_literal(NEW) || ').* ;' ;
-- We won't insert anything on parent table
RETURN NULL ;
END
$$
LANGUAGE plpgsql VOLATILE
COST 1000;
-- Attach trigger to parent table
CREATE TRIGGER reports_insert_trigger
BEFORE INSERT ON reports
FOR EACH ROW EXECUTE PROCEDURE create_partition_and_insert();
Run Code Online (Sandbox Code Playgroud)
用一些数据填充(分区)表;并检查触发器所做的分区:
INSERT INTO
reports (rpt_datetime, rpt_unique_clicks, something_else)
SELECT
d, 1, 'Hello'
FROM
generate_series(timestamp '20170416' - interval '7 days', timestamp '20170416', interval '10 minutes') x(d) ;
-- Check how many partitions we made
SELECT
table_name
FROM
information_schema.tables
WHERE
table_name like 'reports_%'
ORDER BY
table_name;
Run Code Online (Sandbox Code Playgroud)
| 表名 | | :-------------- | | 报告_20170409 | | 报告_20170410 | | 报告_20170411 | | 报告_20170412 | | 报告_20170413 | | 报告_20170414 | | 报告_20170415 | | 报告_20170416 |
此时,我们检查两个不同的查询。第一个确实使用了constant
与rpt_datetime
:
EXPLAIN (ANALYZE)
SELECT
SUM(rpt_unique_clicks)
FROM
reports
WHERE
rpt_datetime >= timestamp '20170416' ;
Run Code Online (Sandbox Code Playgroud)
使用恒定时间戳,仅检查“报告”和适当的分区:
| 查询计划| | :---------------------------------------------------------------- -------------------------------------------------- ------------------- | | 聚合(成本=25.07..25.08行=1宽度=8)(实际时间=0.015..0.015行=1循环=1)| | ->追加(成本=0.00..24.12行=378宽度=4)(实际时间=0.009..0.010行=1循环=1)| | -> 对报告进行顺序扫描(成本=0.00..0.00 行=1 宽度=4)(实际时间=0.003..0.003 行=0 循环=1)| | 过滤器: (rpt_datetime >= '2017-04-16 00:00:00'::timestamp without time zone) | | -> 对 reports_20170416 进行顺序扫描(成本=0.00..24.12行=377宽度=4)(实际时间=0.006..0.007行=1循环=1)| | 过滤器: (rpt_datetime >= '2017-04-16 00:00:00'::timestamp without time zone) | | 规划时间:0.713 ms | | 执行时间:0.040 ms |
如果我们使用SELECT
函数调用来等效(即使该函数调用的结果是常量),则计划完全不同:
EXPLAIN (ANALYZE)
SELECT
SUM(rpt_unique_clicks)
FROM
reports
WHERE
rpt_datetime >= date_trunc('day', now()) ;
Run Code Online (Sandbox Code Playgroud)
| 查询计划| | :---------------------------------------------------------------- -------------------------------------------------- ------------------- | | 聚合(成本=245.74..245.75行=1宽度=8)(实际时间=0.842..0.843行=1循环=1)| | ->追加(成本=0.00..238.20行=3017宽度=4)(实际时间=0.837..0.838行=1循环=1)| | -> 对报告进行顺序扫描(成本=0.00..0.00 行=1 宽度=4)(实际时间=0.003..0.003 行=0 循环=1)| | 过滤器: (rpt_datetime >= date_trunc('day'::text, now())) | | -> 对 reports_20170409 进行顺序扫描(成本=0.00..29.78行=377宽度=4)(实际时间=0.214..0.214行=0循环=1)| | 过滤器: (rpt_datetime >= date_trunc('day'::text, now())) | | 过滤器删除的行数:144 | | -> 对 reports_20170410 进行顺序扫描(成本=0.00..29.78 行=377 宽度=4)(实际时间=0.097..0.097 行=0 循环=1)| | 过滤器: (rpt_datetime >= date_trunc('day'::text, now())) | | 过滤器删除的行数:144 | | -> 对 reports_20170411 进行顺序扫描(成本=0.00..29.78 行=377 宽度=4)(实际时间=0.095..0.095 行=0 循环=1)| | 过滤器: (rpt_datetime >= date_trunc('day'::text, now())) | | 过滤器删除的行数:144 | | -> 对 reports_20170412 进行顺序扫描(成本=0.00..29.78 行=377 宽度=4)(实际时间=0.096..0.096 行=0 循环=1)| | 过滤器: (rpt_datetime >= date_trunc('day'::text, now())) | | 过滤器删除的行数:144 | | -> 对 reports_20170413 进行顺序扫描(成本=0.00..29.78行=377宽度=4)(实际时间=0.131..0.131行=0循环=1)| | 过滤器: (rpt_datetime >= date_trunc('day'::text, now())) | | 过滤器删除的行数:144 | | -> 对 reports_20170414 进行顺序扫描(成本=0.00..29.78 行=377 宽度=4)(实际时间=0.098..0.098 行=0 循环=1)| | 过滤器: (rpt_datetime >= date_trunc('day'::text, now())) | | 过滤器删除的行数:144 | | -> 对 reports_20170415 进行顺序扫描(成本=0.00..29.78 行=377 宽度=4)(实际时间=0.095..0.095 行=0 循环=1)| | 过滤器: (rpt_datetime >= date_trunc('day'::text, now())) | | 过滤器删除的行数:144 | | -> 对 reports_20170416 进行顺序扫描(成本=0.00..29.78 行=377 宽度=4)(实际时间=0.004..0.005 行=1 循环=1)| | 过滤器: (rpt_datetime >= date_trunc('day'::text, now())) | | 规划时间:0.298 ms | | 执行时间:0.892 ms |
dbfiddle在这里
归档时间: |
|
查看次数: |
3196 次 |
最近记录: |