La table reports
est la table partitionnée par jour comme reports_20170414
, reports_20170415
Contrainte SQL est défini comme suit
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
)
Permet de considérer deux types de requête
SELECT SUM(rpt_unique_clicks)
FROM reports WHERE rpt_datetime >= '2017-04-14 00:00:00';
Au-dessus de la requête passe dans les sous-secondes et tout va bien.
SELECT SUM(rpt_unique_clicks)
FROM reports WHERE rpt_datetime >=
date_trunc('day', current_timestamp);
Au contraire, au-dessus de la requête dépasse au moins 15 secondes.
SELECT date_trunc('day', CURRENT_TIMESTAMP), '2017-04-14 00:00:00';
retour
2017-04-14 00:00:00 +00:00 | 2017-04-14 00:00:00
Lorsque j'examine pourquoi ce dernier est long (en utilisant Explimensional Analyze), j'ai terminé le résultat qu'il visite et scanne chaque table (~ 500), mais l'ancien visite seulement reports_20170414
Il y a donc un problème de vérification de contraintes.
Je veux interroger pour aujourd'hui, sans utiliser des déclarations préparées comme la dernière requête. Pourquoi date_trunc('day', CURRENT_TIMESTAMP)
n'est pas équivalente à 2017-04-14 00:00:00
?
Je ne peux pas répondre pleinement à la raison pour laquelle date_trunc('day', CURRENT_TIMESTAMP)
n'est pas équivalente à une constante ... même si les deux CURRENT_TIMESTAMP
et date_trunc
sont définis comme IMMUTABLE
.
Mais je pense que nous pouvons faire un expérimental expérimental estimé : apparemment, le planificateur PostgreSQL N'évalue pas Fonctions. En tant que tel, il n'a pas de bon moyen de savoir quelles cloisons pour vérifier et constitue un plan qui les vérifie tous.
Vérification expérimentale
Nous créons une table de base (parent):
-- 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
) ;
Nous créons un déclencheur d'insertion de partition automatique:
-- 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();
Remplir la table (partitionnée) avec certaines données; et vérifiez les partitions que le déclencheur a fait:
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 | [.____] | : -------------- | rapports_20170409 | [.____] | rapports_20170410 | [.____] | rapports_20170411 | | rapports_20170412 | [.____] | rapports_20170413 | | rapports_20170414 | | rapports_20170415 | | rapports_20170416 |
À ce stade, nous vérifions deux requêtes différentes. Le premier utilise un constant
par rapport à rpt_datetime
:
EXPLAIN (ANALYZE)
SELECT
SUM(rpt_unique_clicks)
FROM
reports
WHERE
rpt_datetime >= timestamp '20170416' ;
En utilisant un horodatage constant, seuls "rapports" et la partition appropriée sont vérifiées:
[.____] | Plan de requête | [.____] | : ------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ --------------------- | [.____] | Agrégat (coût = 25.07..25.08 rangées = 1 largeur = 8) (temps réel = 0,015..0.015 rangées = 1 boucles = 1) | [.____] | -> Ajoutez (coût = 0,00..24,12 lignes = 378 largeur = 4) (temps réel = 0,009..0.010 rangées = 1 boucles = 1) | [.____] | -> SEQ Scan sur les rapports (Coût = 0,00..0,00 lignes = 1 largeur = 4) (temps réel = 0,003..0.003 lignes = 0 boucles = 1) | [.____] | Filtre: (RPT_DateTime> = '2017-04-16 00:00:00' :: horodatage sans fuseau horaire) | [.____] | -> SEQ Scan sur rapports_20170416 (coût = 0,00..24.12 lignes = 377 largeur = 4) (temps réel = 0,006..0.007 lignes = 1 boucles = 1) | [.____] | Filtre: (RPT_DateTime> = '2017-04-16 00:00:00' :: horodatage sans fuseau horaire) | [.____] | Temps de planification: 0,713 ms | [.____] | Heure d'exécution: 0,040 ms | [.____]
Si nous utilisons l'équivalent SELECT
à l'aide d'un appel de fonction (même si le résultat de cet appel de fonction est une constante), le plan est complètement différent:
EXPLAIN (ANALYZE)
SELECT
SUM(rpt_unique_clicks)
FROM
reports
WHERE
rpt_datetime >= date_trunc('day', now()) ;
[.____] | Plan de requête | [.____] | : ------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ --------------------- | [.____] | Agrégat (coût = 245.74..245.75 lignes = 1 largeur = 8) (temps réel = 0,842..0.843 rangées = 1 boucles = 1) | [.____] | -> Ajoutez (coût = 0,00..238.20 lignes = 3017 largeur = 4) (temps réel = 0,837..0.838 rangées = 1 boucles = 1) | [.____] | -> SEQ Scan sur les rapports (Coût = 0,00..0,00 lignes = 1 largeur = 4) (temps réel = 0,003..0.003 lignes = 0 boucles = 1) | [.____] | Filtre: (rpt_datetime> = date_trunch ("jour" :: texte, maintenant ())) | [.____] | -> SEQ Scan sur rapports_20170409 (coût = 0,00..29,78 lignes = 377 largeur = 4) (temps réel = 0,214..0,214 rangées = 0 boucles = 1) | [.____] | Filtre: (rpt_datetime> = date_trunch ("jour" :: texte, maintenant ())) | [.____] | Lignes enlevées par filtre: 144 | | -> SEQ Scan sur rapports_20170410 (Coût = 0,00..29,78 lignes = 377 largeur = 4) (temps réel = 0,097..0.097 rangées = 0 boucles = 1) | [.____] | Filtre: (rpt_datetime> = date_trunch ("jour" :: texte, maintenant ())) | [.____] | Lignes enlevées par filtre: 144 | | -> SEQ Scan sur rapports_20170411 (coût = 0,00..29.78 lignes = 377 largeur = 4) (temps réel = 0,095..0.095 rangées = 0 boucles = 1) | [.____] | Filtre: (rpt_datetime> = date_trunch ("jour" :: texte, maintenant ())) | [.____] | Lignes enlevées par filtre: 144 | | -> SEQ Scan sur rapports_20170412 (coût = 0,00..29,78 lignes = 377 largeur = 4) (temps réel = 0,096..0.096 lignes = 0 boucles = 0) | [.____] | Filtre: (rpt_datetime> = date_trunch ("jour" :: texte, maintenant ())) | [.____] | Lignes enlevées par filtre: 144 | | -> SEQ Scan sur rapports_20170413 (coût = 0,00..29,78 lignes = 377 largeur = 4) (temps réel = 0,131..0,131 lignes = 0 boucles = 1) | [.____] | Filtre: (rpt_datetime> = date_trunch ("jour" :: texte, maintenant ())) | [.____] | Lignes enlevées par filtre: 144 | | -> SEQ Scan sur rapports_20170414 (coût = 0,00..29,78 lignes = 377 largeur = 4) (temps réel = 0,098..0.098 rangées = 0 boucles = 1) | [.____] | Filtre: (rpt_datetime> = date_trunch ("jour" :: texte, maintenant ())) | [.____] | Lignes enlevées par filtre: 144 | | -> SEQ Scan sur rapports_20170415 (coût = 0,00..29,78 lignes = 377 largeur = 4) (temps réel = 0,095..0.095 rangées = 0 boucles = 1) | [.____] | Filtre: (rpt_datetime> = date_trunch ("jour" :: texte, maintenant ())) | [.____] | Lignes enlevées par filtre: 144 | | -> SEQ Scan sur rapports_20170416 (coût = 0,00..29,78 lignes = 377 largeur = 4) (temps réel = 0,004..0,005 rangées = 1 boucles = 1) | [.____] | Filtre: (rpt_datetime> = date_trunch ("jour" :: texte, maintenant ())) | [.____] | Temps de planification: 0,298 ms | [.____] | Heure d'exécution: 0.892 MS | [.____]
dbfiddle --- (ICI