テーブル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
)
_
2つのタイプのクエリを考えてみましょう
_SELECT SUM(rpt_unique_clicks)
FROM reports WHERE rpt_datetime >= '2017-04-14 00:00:00';
_
上記のクエリは1秒以内に実行され、すべて正常です。
_SELECT SUM(rpt_unique_clicks)
FROM reports WHERE rpt_datetime >=
date_trunc('day', current_timestamp);
_
逆に、上記のクエリは少なくとも15秒実行されます。
_SELECT date_trunc('day', CURRENT_TIMESTAMP), '2017-04-14 00:00:00';
_
戻り値
_2017-04-14 00:00:00 +00:00 | 2017-04-14 00:00:00
_
後者が(explain analyzeを使用して)長く実行される理由を調べると、すべてのテーブルを訪問してスキャンする結果(〜500)を終了しましたが、前者は_reports_20170414
_のみを訪問するため、制約チェックに問題があります。
後者のクエリのように準備されたステートメントを使用せずに、今日のクエリを実行したいと思います。なぜdate_trunc('day', CURRENT_TIMESTAMP)
は_2017-04-14 00:00:00
_と同等ではないのですか?
_CURRENT_TIMESTAMP
_と_date_trunc
_の両方がIMMUTABLE
として定義されている場合でも、date_trunc('day', CURRENT_TIMESTAMP)
が定数と等しくない理由について完全に応答することはできません。
しかし、私たちは実験的な推測を行うことができると思います:どうやら、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
) ;
_
自動パーティション挿入トリガーを作成します。
_ -- 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();
_
(パーティション化された)テーブルにデータを入力します。トリガーが作成したパーティションを確認します。
_ 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;
_
| table_name | | :------------ | reports_20170409 | | reports_20170410 | | reports_20170411 | | reports_20170412 | | reports_20170413 | | reports_20170414 | | reports_20170415 | | reports_20170416 |
この時点で、2つの異なるクエリを確認します。最初のものは、_rpt_datetime
_と比較してconstant
を使用します。
_ EXPLAIN (ANALYZE)
SELECT
SUM(rpt_unique_clicks)
FROM
reports
WHERE
rpt_datetime >= timestamp '20170416' ;
_
一定のタイムスタンプを使用して、「レポート」と適切なパーティションのみがチェックされます。
|クエリプラン| | :------------------------------------------------- -------------------------------------------------- ------------------- | |集計(コスト= 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)| | ->レポートのシーケンススキャン_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()) ;
_
|クエリプラン| | :------------------------------------------------- -------------------------------------------------- ------------------- | |集計(コスト= 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()))| | ->レポートのシーケンススキャン_20170409(コスト= 0.00..29.78行= 377幅= 4)(実際の時間= 0.214..0.214行= 0ループ= 1)| |フィルター:(rpt_datetime> = date_trunc( 'day' :: text、now()))| |フィルターによって削除された行:144 | | ->レポートのシーケンススキャン_20170410(コスト= 0.00..29.78行= 377幅= 4)(実際の時間= 0.097..0.097行= 0ループ= 1)| |フィルター:(rpt_datetime> = date_trunc( 'day' :: text、now()))| |フィルターによって削除された行:144 | | ->レポートのシーケンススキャン_20170411(コスト= 0.00..29.78行= 377幅= 4)(実際の時間= 0.095..0.095行= 0ループ= 1)| |フィルター:(rpt_datetime> = date_trunc( 'day' :: text、now()))| |フィルターによって削除された行:144 | | ->レポートのシーケンススキャン_20170412(コスト= 0.00..29.78行= 377幅= 4)(実際の時間= 0.096..0.096行= 0ループ= 1)| |フィルター:(rpt_datetime> = date_trunc( 'day' :: text、now()))| |フィルターによって削除された行:144 | | ->レポートのシーケンススキャン_20170413(コスト= 0.00..29.78行= 377幅= 4)(実際の時間= 0.131..0.131行= 0ループ= 1)| |フィルター:(rpt_datetime> = date_trunc( 'day' :: text、now()))| |フィルターによって削除された行:144 | | ->レポートのシーケンススキャン_20170414(コスト= 0.00..29.78行= 377幅= 4)(実際の時間= 0.098..0.098行= 0ループ= 1)| |フィルター:(rpt_datetime> = date_trunc( 'day' :: text、now()))| |フィルターによって削除された行:144 | | ->レポートのシーケンススキャン_20170415(コスト= 0.00..29.78行= 377幅= 4)(実際の時間= 0.095..0.095行= 0ループ= 1)| |フィルター:(rpt_datetime> = date_trunc( 'day' :: text、now()))| |フィルターによって削除された行:144 | | ->レポートのシーケンススキャン_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 ---(ここ