J'effectue une mise à jour où j'ai besoin d'une égalité exacte sur une tstzrange
variable. ~ 1 M de lignes sont modifiées et la requête prend environ 13 minutes. Le résultat de EXPLAIN ANALYZE
peut être vu ici , et les résultats réels sont extrêmement différents de ceux estimés par le planificateur de requêtes. Le problème est que l'analyse d'index t_range
attend qu'une seule ligne soit renvoyée.
Cela semble être lié au fait que les statistiques sur les types de plage sont stockées différemment de celles des autres types. En regardant la pg_stats
vue de la colonne, n_distinct
-1 est et les autres champs (par exemple most_common_vals
, most_common_freqs
) sont vides.
Cependant, il doit y avoir des statistiques stockées t_range
quelque part. Une mise à jour extrêmement similaire où j'utilise un «dedans» sur t_range au lieu d'une égalité exacte prend environ 4 minutes à effectuer et utilise un plan de requête substantiellement différent (voir ici ). Le deuxième plan de requête est logique pour moi car chaque ligne de la table temporaire et une fraction substantielle de la table d'historique seront utilisées. Plus important encore, le planificateur de requêtes prédit un nombre approximativement correct de lignes pour le filtre activé t_range
.
La distribution de t_range
est un peu inhabituelle. J'utilise cette table pour stocker l'état historique d'une autre table, et les modifications apportées à l'autre table se produisent simultanément dans de grands vidages, il n'y a donc pas beaucoup de valeurs distinctes de t_range
. Voici les chiffres correspondant à chacune des valeurs uniques de t_range
:
t_range | count
-------------------------------------------------------------------+---------
["2014-06-12 20:58:21.447478+00","2014-06-27 07:00:00+00") | 994676
["2014-06-12 20:58:21.447478+00","2014-08-01 01:22:14.621887+00") | 36791
["2014-06-27 07:00:00+00","2014-08-01 07:00:01+00") | 1000403
["2014-06-27 07:00:00+00",infinity) | 36791
["2014-08-01 07:00:01+00",infinity) | 999753
Les décomptes pour les éléments t_range
ci-dessus sont terminés, la cardinalité est donc de ~ 3M (dont ~ 1M seront affectés par l'une ou l'autre des requêtes de mise à jour).
Pourquoi la requête 1 fonctionne-t-elle beaucoup moins bien que la requête 2? Dans mon cas, la requête 2 est un bon substitut, mais si une égalité de plage exacte était vraiment requise, comment pourrais-je amener Postgres à utiliser un plan de requête plus intelligent?
Définition de table avec index (suppression de colonnes non pertinentes):
Column | Type | Modifiers
---------------------+-----------+------------------------------------------------------------------------------
history_id | integer | not null default nextval('gtfs_stop_times_history_history_id_seq'::regclass)
t_range | tstzrange | not null
trip_id | text | not null
stop_sequence | integer | not null
shape_dist_traveled | real |
Indexes:
"gtfs_stop_times_history_pkey" PRIMARY KEY, btree (history_id)
"gtfs_stop_times_history_t_range" gist (t_range)
"gtfs_stop_times_history_trip_id" btree (trip_id)
Requête 1:
UPDATE gtfs_stop_times_history sth
SET shape_dist_traveled = tt.shape_dist_traveled
FROM gtfs_stop_times_temp tt
WHERE sth.trip_id = tt.trip_id
AND sth.stop_sequence = tt.stop_sequence
AND sth.t_range = '["2014-08-01 07:00:01+00",infinity)'::tstzrange;
Requête 2:
UPDATE gtfs_stop_times_history sth
SET shape_dist_traveled = tt.shape_dist_traveled
FROM gtfs_stop_times_temp tt
WHERE sth.trip_id = tt.trip_id
AND sth.stop_sequence = tt.stop_sequence
AND '2014-08-01 07:00:01+00'::timestamptz <@ sth.t_range;
Q1 met à jour 999753 lignes et Q2 met à jour 999753 + 36791 = 1036544 (c'est-à-dire que la table temporaire est telle que chaque ligne correspondant à la condition de plage de temps est mise à jour).
J'ai essayé cette requête en réponse au commentaire de @ ypercube :
Requête 3:
UPDATE gtfs_stop_times_history sth
SET shape_dist_traveled = tt.shape_dist_traveled
FROM gtfs_stop_times_temp tt
WHERE sth.trip_id = tt.trip_id
AND sth.stop_sequence = tt.stop_sequence
AND sth.t_range <@ '["2014-08-01 07:00:01+00",infinity)'::tstzrange
AND '["2014-08-01 07:00:01+00",infinity)'::tstzrange <@ sth.t_range;
Le plan de requête et les résultats (voir ici ) étaient intermédiaires entre les deux cas précédents (~ 6 minutes).
2016/02/05 EDIT
N'ayant plus accès aux données au bout d'un an et demi, j'ai créé une table de test avec la même structure (sans index) et une cardinalité similaire. La réponse de jjanes a proposé que la cause pourrait être l'ordre de la table temporaire utilisée pour la mise à jour. Je n'ai pas pu tester l'hypothèse directement car je n'y ai pas accès track_io_timing
(en utilisant Amazon RDS).
Les résultats globaux ont été beaucoup plus rapides (d'un facteur de plusieurs). Je suppose que cela est dû à la suppression des indices, conformément à la réponse d' Erwin .
Dans ce cas de test, les requêtes 1 et 2 prenaient essentiellement le même temps, car elles utilisaient toutes deux la jointure de fusion. C'est-à-dire que je n'ai pas pu déclencher ce qui a poussé Postgres à choisir la jointure de hachage, donc je ne sais pas pourquoi Postgres a choisi la jointure de hachage peu performante en premier lieu.
(lower(t_range),upper(t_range))
depuis que vous vérifiez l'égalité.
(a = b)
à deux « contient » conditions:(a @> b AND b @> a)
? Le plan change-t-il?