web-dev-qa-db-fra.com

PostgreSQL Table partitionné Problème de contrainte Timestamptz

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?

5
onesvat

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

4
joanolo