Schéma:
Column | Type
----------------------+--------------------------
id | integer
event_id | integer
started_at | timestamp with time zone
ended_at | timestamp with time zone
created_at | timestamp with time zone
"event_seat_state_lookup_pkey" PRIMARY KEY, btree (id)
"event_seating_lookup_created_at_idx" btree (created_at)
"event_seating_lookup_created_at_idx2" Gist (created_at)
Mettre en doute:
SELECT id
FROM event_seating_lookup esl1
WHERE
tstzrange(now() - interval '1 hour', now() + interval '1 hour', '[)') @> esl1.created_at;
Expliquez Analyser:
Table avec <100K lignes.
Seq Scan on event_seating_lookup esl1 (cost=0.00..1550.30 rows=148 width=4) (actual time=0.013..19.956 rows=29103 loops=1)
Filter: (tstzrange((now() - '01:00:00'::interval), (now() + '01:00:00'::interval), '[)'::text) @> created_at)
Buffers: shared hit=809
Planning Time: 0.110 ms
Execution Time: 21.942 ms
Tableau avec 1m + rangées:
Seq Scan on event_seating_lookup esl1 (cost=10000000000.00..10000042152.75 rows=5832 width=4) (actual time=0.009..621.895 rows=1166413 loops=1)
Filter: (tstzrange((now() - '01:00:00'::interval), (now() + '01:00:00'::interval), '[)'::text) @> created_at)
Buffers: shared hit=12995
Planning Time: 0.092 ms
Execution Time: 697.927 ms
J'ai essayé:
VACUUM FULL event_seating_lookup;
VACUUM event_seating_lookup;
VACUUM ANALYZE event_seating_lookup;
SET enable_seqscan = OFF;
Problème:
event_seating_lookup_created_at_idx
ou alors event_seating_lookup_created_at_idx2
Les index ne sont pas utilisés.
Remarques:
btree_Gist
L'extension est installée.created_at timestamp without time zone
et utiliser tsrange
; même résultat.>=
, <
Les chèques feraient l'utilisation de l'indice BTREE. La question est de savoir quelle est la raison pour laquelle l'index n'est pas utilisé avec l'opérateur de confinement tstzrange
et s'il existe un moyen de le faire fonctionner.En ce qui concerne mes recherches, PostgreSQL n'est pas en mesure de réécrire le contrôle de confinement dans une expression pouvant être assortie à l'aide de l'indice BTREE, c'est-à-dire.
esl1.created_at >= now() - interval '1 hour' AND
esl1.created_at < now() + interval '1 hour'
lorsqu'il est écrit de cette manière, la requête est exécutée à l'aide d'index:
Index Scan using event_seating_lookup_created_at_idx on event_seating_lookup esl1 (cost=0.44..12623.56 rows=18084 width=4) (actual time=0.013..57.084 rows=70149 loops=1)
Index Cond: ((created_at >= (now() - '01:00:00'::interval)) AND (created_at < (now() + '01:00:00'::interval)))
Planning Time: 0.223 ms
Execution Time: 62.209 ms
Comme je préfère la syntaxe de la requête de confinement sur cette dernière forme, j'ai étudié des alternatives possibles. Ce qui est venu, c'est que je puisse écrire une procédure qui fera en ligne la condition pour moi:
CREATE OR REPLACE FUNCTION in_range(timestamptz, tstzrange)
RETURNS boolean AS $$
SELECT
(
$1 >= lower($2) AND
$1 <= upper($2) AND
(
upper_inc($2) OR
$1 < upper($2)
) AND
(
lower_inc($2) OR
$1 > lower($2)
)
)
$$
language sql;
La raison de la première correspondance sur la condition $1 >= lower($2) AND $1 <= upper($2)
_ condition puis vérifiez le upper_inc
Et lower_inc
Les contraintes sont d'abord bénéficier d'une analyse de la plage, puis de filtrer le résultat.
La question est de savoir quelle est la raison pour laquelle l'index n'est pas utilisé avec l'opérateur de confinement
tstzrange
et s'il existe un moyen de le faire fonctionner.
Le raison est assez trivial. Les index b-arbres ne supportent pas l'opérateur de confinement @>
. Ni pour types de gamme comme tstzrange
ni pour aucun autre type (y compris les types de réseau).
... Une classe opérateur BTREE doit fournir cinq opérateurs de comparaison,
<
,<=
,=
,>=
et>
.
Et les index gist ne prennent pas en charge <
, <=
,> =
, >=
et >
. Voir ces chapitres du manuel:
Dans les index des postgres sont liés aux opérateurs (qui sont mis en œuvre pour certains types), pas des types de données seules ou des fonctions ou autre chose. En rapport:
L'indice gist event_seating_lookup_created_at_idx2
vous avez est inutile . Il est créé sur la colonne timestamptz
created_at
. Un tel index gist serait utile pour A plage Type (le sens opposé de la logique).
Création d'un index GIST sur une colonne timestamptz
est uniquement possible car vous avez installé l'extension de btree_Gist
supplémentaire pour permettre de tels index inutiles. (Il existe des applications utiles pour les indices multicolumnistes ou les contraintes d'exclusion ...) En stock Postgres, vous obtiendriez une erreur:
Erreur: Type de données L'horodatage avec le fuseau horaire n'a pas de classe d'opérateur par défaut pour la méthode d'accès "GIST"
Donc, alors qu'il est logiquement valide et techniquement possible d'utiliser un index BTTREE (ou un indice de gist) pour la requête, le boîtier n'est pas implémenté: Aucun support d'index pour timestamptz <@ tstzrange
(où timestamptz
serait l'expression indexée!). Il peut être résolu avec <
, <=
, >
, >=
plus efficacement. Je suppose donc qu'aucun développeur ne ressentait (ou ne ressentira) la nécessité de la mettre en œuvre.
Votre implémentation a du sens pour moi. Cela fait ce que vous voulez et due à Fonction Inlinge Utilise un indice de BTTREE uni sur la colonne TIMESTAMP - event_seating_lookup_created_at_idx
dans votre exemple. Pour les appels avec une plage constante (comme dans un appel unique), je suggère cette version modifiée:
CREATE OR REPLACE FUNCTION in_range(timestamptz, tstzrange)
RETURNS boolean AS
$func$
SELECT CASE WHEN lower_inc($2) THEN $1 >= lower($2) ELSE $1 > lower($2) END
AND CASE WHEN upper_inc($2) THEN $1 <= upper($2) ELSE $1 < upper($2) END
$func$ LANGUAGE sql IMMUTABLE;
Déclarez-le IMMUTABLE
, car c'est en fait. Ne pas aider à fonctionner la fonction (peut même l'empêcher si la déclaration est fausse) mais pour d'autres gains. En rapport:
Il peut être inliné et utilise l'index comme votre version. La différence: celle-ci supprime une condition d'index redondante aux limites exclusives. Heureusement, votre considération à cet égard est légèrement hors cible:
La raison de la première correspondance sur
the $1 >= lower($2) AND $1 <= upper($2)
Condition puis de la vérification des contraintesupper_inc
etlower_inc
consiste à bénéficier d'une balayage de la plage, puis à filtrer le résultat.
Postgres 11 (au moins) est encore plus intelligent que cela. Je ne vois pas Filter
étape pour votre version. Pour les limites [)
par défaut (comme dans votre exemple), je reçois ce plan de requête (rupture de ligne dans les conditions ajoutées par moi):
-> Index Only Scan using foo_idx on foo (actual rows=5206 loops=1)
Index Cond: ((datetime >= '2018-09-05 22:00:00+00'::timestamp with time zone)
AND (datetime <= '2018-09-05 22:30:00+00'::timestamp with time zone)
AND (datetime < '2018-09-05 22:30:00+00'::timestamp with time zone))
Une étape réelle Filter
pourrait ajouter un coût plus substantiel pour les cas d'angle avec de nombreux hits sur une liaison exclue. Ceux-ci seraient récupérés de l'indice, puis jetés. Assez pertinent pour les gammes de temps où les valeurs se retrouvent souvent sur des limites - comme des horodatages à l'heure complète.
La différence effective est la suivante: est mineure, mais pourquoi ne pas le prendre?
-> Index Only Scan using foo_idx on foo (actual rows=5206 loops=1)
Index Cond: ((datetime >= '2018-09-05 22:00:00+00'::timestamp with time zone)
AND (datetime < '2018-09-05 22:30:00+00'::timestamp with time zone))