J'utilise PostgresSQL 9.2 et j'ai une relation de 12 colonnes avec environ 6 700 000 lignes. Il contient des nœuds dans un espace 3D, chacun référençant un utilisateur (qui l'a créé). Pour demander quel utilisateur a créé le nombre de nœuds, je fais ce qui suit (ajouté explain analyze
pour plus d'informations):
EXPLAIN ANALYZE SELECT user_id, count(user_id) FROM treenode WHERE project_id=1 GROUP BY user_id;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------
HashAggregate (cost=253668.70..253669.07 rows=37 width=8) (actual time=1747.620..1747.623 rows=38 loops=1)
-> Seq Scan on treenode (cost=0.00..220278.79 rows=6677983 width=8) (actual time=0.019..886.803 rows=6677983 loops=1)
Filter: (project_id = 1)
Total runtime: 1747.653 ms
Comme vous pouvez le voir, cela prend environ 1,7 seconde. Ce n'est pas trop mal compte tenu de la quantité de données, mais je me demande si cela peut être amélioré. J'ai essayé d'ajouter un index BTree sur la colonne utilisateur, mais cela n'a aidé en aucune façon.
Avez-vous des suggestions alternatives?
Par souci d'exhaustivité, voici la définition complète de la table avec tous ses indices (sans contraintes, références et déclencheurs de clé étrangère):
Column | Type | Modifiers
---------------+--------------------------+------------------------------------------------------
id | bigint | not null default nextval('concept_id_seq'::regclass)
user_id | bigint | not null
creation_time | timestamp with time zone | not null default now()
edition_time | timestamp with time zone | not null default now()
project_id | bigint | not null
location | double3d | not null
reviewer_id | integer | not null default (-1)
review_time | timestamp with time zone |
editor_id | integer |
parent_id | bigint |
radius | double precision | not null default 0
confidence | integer | not null default 5
skeleton_id | bigint |
Indexes:
"treenode_pkey" PRIMARY KEY, btree (id)
"treenode_id_key" UNIQUE CONSTRAINT, btree (id)
"skeleton_id_treenode_index" btree (skeleton_id)
"treenode_editor_index" btree (editor_id)
"treenode_location_x_index" btree (((location).x))
"treenode_location_y_index" btree (((location).y))
"treenode_location_z_index" btree (((location).z))
"treenode_parent_id" btree (parent_id)
"treenode_user_index" btree (user_id)
Edit: Voici le résultat, lorsque j'utilise la requête (et l'index) proposée par @ypercube (la requête prend environ 5,3 secondes sans EXPLAIN ANALYZE
):
EXPLAIN ANALYZE SELECT u.id, ( SELECT COUNT(*) FROM treenode AS t WHERE t.project_id=1 AND t.user_id = u.id ) AS number_of_nodes FROM auth_user As u;
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------------
Seq Scan on auth_user u (cost=0.00..6987937.85 rows=46 width=4) (actual time=29.934..5556.147 rows=46 loops=1)
SubPlan 1
-> Aggregate (cost=151911.65..151911.66 rows=1 width=0) (actual time=120.780..120.780 rows=1 loops=46)
-> Bitmap Heap Scan on treenode t (cost=4634.41..151460.44 rows=180486 width=0) (actual time=13.785..114.021 rows=145174 loops=46)
Recheck Cond: ((project_id = 1) AND (user_id = u.id))
Rows Removed by Index Recheck: 461076
-> Bitmap Index Scan on treenode_user_index (cost=0.00..4589.29 rows=180486 width=0) (actual time=13.082..13.082 rows=145174 loops=46)
Index Cond: ((project_id = 1) AND (user_id = u.id))
Total runtime: 5556.190 ms
(9 rows)
Time: 5556.804 ms
Edit 2: Voici le résultat, lorsque j'utilise un index
on project_id, user_id
(mais pas encore d'optimisation de schéma) comme l'a suggéré @ erwin-brandstetter (la requête s'exécute avec 1,5 seconde à la même vitesse que ma requête d'origine):
EXPLAIN ANALYZE SELECT user_id, count(user_id) as ct FROM treenode WHERE project_id=1 GROUP BY user_id;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------
HashAggregate (cost=253670.88..253671.24 rows=37 width=8) (actual time=1807.334..1807.339 rows=38 loops=1)
-> Seq Scan on treenode (cost=0.00..220280.62 rows=6678050 width=8) (actual time=0.183..893.491 rows=6678050 loops=1)
Filter: (project_id = 1)
Total runtime: 1807.368 ms
(4 rows)
Users
avecuser_id
comme clé primaire?project_id
etuser_id
? Le tableau est-il mis à jour en continu ou pourriez-vous travailler avec une vue matérialisée (pendant un certain temps)?Réponses:
Le problème principal est l'index manquant. Mais il y a plus.
Vous avez plusieurs
bigint
colonnes. Probablement exagéré. En règle générale,integer
est plus que suffisant pour les colonnes commeproject_id
etuser_id
. Cela aiderait également l'élément suivant.Tout en optimisant la définition de la table, tenez compte de cette réponse connexe, en mettant l'accent sur l'alignement et le remplissage des données . Mais la plupart du reste s'applique également:
L' éléphant dans la pièce : il n'y a pas d' index sur
project_id
. Créer une. C'est plus important que le reste de cette réponse.Tout en y étant, faites-en un index multicolonne:
Si vous avez suivi mes conseils, ce
integer
serait parfait ici:user_id
est définiNOT NULL
,count(user_id)
est donc équivalent àcount(*)
, mais ce dernier est un peu plus court et plus rapide. (Dans cette requête spécifique, cela s'appliquerait même sansuser_id
être définiNOT NULL
.)id
est déjà la clé primaire, laUNIQUE
contrainte supplémentaire est le ballast inutile . Laisse tomber:À part: je ne l'utiliserais pas
id
comme nom de colonne. Utilisez quelque chose de descriptif commetreenode_id
.Ajout d'informations
Q:
How many different project_id and user_id?
A:
not more than five different project_id
.Cela signifie que Postgres doit lire environ 20% de l'ensemble du tableau pour satisfaire votre requête. À moins qu'il ne puisse utiliser une analyse d'index uniquement , une analyse séquentielle sur la table sera plus rapide qu'impliquant n'importe quel index. Plus de performances à gagner ici - sauf en optimisant les paramètres de la table et du serveur.
Quant à l' analyse d'index uniquement : pour voir à quel point cela peut être efficace, exécutez
VACUUM ANALYZE
si vous pouvez vous le permettre (verrouille la table exclusivement). Réessayez ensuite votre requête. Il devrait maintenant être modérément plus rapide en utilisant uniquement l'index. Lisez d'abord cette réponse connexe:Ainsi que la page de manuel ajoutée avec Postgres 9.6 et le Postgres Wiki sur les analyses d'index uniquement .
la source
user_id
et vousproject_id
integer
devriez être plus que suffisant. Utilisercount(*)
au lieu d'count(user_id)
économiser environ 70 ms ici, c'est bon à savoir. J'ai ajouté laEXPLAIN ANALYZE
requête après avoir ajouté votre suggestionindex
au premier message. Cependant, cela n'améliore pas les performances (mais ne nuit pas non plus). Il semble que leindex
n'est pas utilisé du tout. Je testerai bientôt les optimisations de schéma.seqscan
, l'index est utilisé (Index Only Scan using treenode_project_id_user_id_index on treenode
), mais la requête prend alors environ 2,5 secondes (soit environ 1 seconde de plus qu'avec seqscan).Je voudrais d'abord ajouter un index
(project_id, user_id)
, puis dans la version 9.3, essayez cette requête:En 9.2, essayez celui-ci:
Je suppose que vous avez une
users
table. Sinon, remplacezusers
par:(SELECT DISTINCT user_id FROM treenode)
la source
CREATE INDEX treenode_user_index ON treenode USING btree (project_id, user_id);
mais j'ai également essayé sans laUSING
clause. Dois-je manquer quelque chose?users
tableau et combien de lignes la requête renvoie-t-elle (donc combien d'utilisateurs y en a-t-ilproject_id=1
)? Pouvez-vous montrer l'explication de cette requête, après avoir ajouté l'index?index
en place. Désolé pour la confusion. Dans monusers
tableau, j'ai 46 entrées. La requête ne renvoie que 9 lignes. Étonnamment,SELECT DISTINCT user_id FROM treenode WHERE project_id=1;
renvoie 38 lignes. J'ai ajouté leexplain
à mon premier message. Et pour éviter toute confusion: mausers
table est en fait appeléeauth_user
.SELECT DISTINCT user_id FROM treenode WHERE project_id=1;
renvoyer 38 lignes alors que les requêtes n'en renvoient que 9. Buffled.SET enable_seqscan = OFF; (Query); SET enable_seqscan = ON;