J'ai une table avec 7,2 millions de tuples qui ressemble à ceci:
table public.methods
column | type | attributes
--------+-----------------------+----------------------------------------------------
id | integer | not null DEFAULT nextval('methodkey'::regclass)
hash | character varying(32) | not null
string | character varying | not null
method | character varying | not null
file | character varying | not null
type | character varying | not null
Indexes:
"methods_pkey" PRIMARY KEY, btree (id)
"methodhash" btree (hash)
Maintenant, je veux sélectionner quelques valeurs mais la requête est incroyablement lente:
db=# explain
select hash, string, count(method)
from methods
where hash not in
(select hash from nostring)
group by hash, string
order by count(method) desc;
QUERY PLAN
----------------------------------------------------------------------------------------
Sort (cost=160245190041.10..160245190962.07 rows=368391 width=182)
Sort Key: (count(methods.method))
-> GroupAggregate (cost=160245017241.77..160245057764.73 rows=368391 width=182)
-> Sort (cost=160245017241.77..160245026451.53 rows=3683905 width=182)
Sort Key: methods.hash, methods.string
-> Seq Scan on methods (cost=0.00..160243305942.27 rows=3683905 width=182)
Filter: (NOT (SubPlan 1))
SubPlan 1
-> Materialize (cost=0.00..41071.54 rows=970636 width=33)
-> Seq Scan on nostring (cost=0.00..28634.36 rows=970636 width=33)
La hash
colonne est le hachage md5 string
et possède un index. Je pense donc que mon problème est que la table entière est triée par identifiant et non par hachage, il faut donc un certain temps pour la trier d'abord, puis la regrouper?
Le tableau nostring
ne contient qu'une liste de hachages que je ne veux pas avoir. Mais j'ai besoin des deux tables pour avoir toutes les valeurs. Ce n'est donc pas une option pour les supprimer.
info supplémentaire: aucune des colonnes ne peut être nulle (corrigé cela dans la définition de la table) et j'utilise postgresql 9.2.
NULL
valeurs dans la colonnemethod
? Y a-t-il des doublonsstring
?Réponses:
La réponse de
LEFT JOIN
in @ dezso devrait être bonne. Un index, cependant, ne sera guère utile (en soi), car la requête doit de toute façon lire la table entière - l'exception étant les analyses d'index uniquement dans Postgres 9.2+ et les conditions favorables, voir ci-dessous.Exécutez
EXPLAIN ANALYZE
sur la requête. Plusieurs fois pour exclure les effets d'encaissement et le bruit. Comparez les meilleurs résultats.Créez un index multi-colonnes qui correspond à votre requête:
Attendez? Après avoir dit qu'un index ne serait pas utile? Eh bien, nous en avons besoin
CLUSTER
:Relancez
EXPLAIN ANALYZE
. Plus rapide? Ça devrait être.CLUSTER
est une opération ponctuelle pour réécrire la table entière dans l'ordre de l'index utilisé. C'est aussi effectivement unVACUUM FULL
. Si vous voulez en être sûr, vous effectuerez un pré-test avecVACUUM FULL
seul pour voir ce qui peut être attribué à cela.Si votre table voit beaucoup d'opérations d'écriture, l'effet se dégradera avec le temps. Planifiez
CLUSTER
en dehors des heures de travail pour restaurer l'effet. Le réglage fin dépend de votre cas d'utilisation exact. Le manuel surCLUSTER
.CLUSTER
est un outil assez grossier, a besoin d'un verrou exclusif sur la table. Si vous ne pouvez pas vous le permettre, réfléchissez à celuipg_repack
qui peut faire de même sans verrouillage exclusif. Plus dans cette réponse ultérieure:Si le pourcentage de
NULL
valeurs dans la colonnemethod
est élevé (plus de ~ 20%, selon la taille réelle des lignes), un index partiel devrait aider:(Votre mise à jour ultérieure montre que vos colonnes le sont
NOT NULL
, donc sans objet.)Si vous exécutez PostgreSQL 9.2 ou version ultérieure (comme l' a commenté @deszo ), les index présentés peuvent être utiles sans
CLUSTER
que le planificateur puisse utiliser des analyses d'index uniquement . Applicable uniquement dans des conditions favorables: aucune opération d'écriture qui affecterait la carte de visibilité car la dernièreVACUUM
colonne et toutes les colonnes de la requête doivent être couvertes par l'index. Les tables en lecture seule peuvent l'utiliser à tout moment, tandis que les tables fortement écrites sont limitées. Plus de détails dans le wiki Postgres.L'index partiel mentionné ci-dessus pourrait être encore plus utile dans ce cas.
Si , d'autre part, il n'y a pas de
NULL
valeurs dans la colonnemethod
, vous devez1.) la définir
NOT NULL
et2.) utiliser à la
count(*)
place decount(method)
, c'est légèrement plus rapide et fait de même en l'absence deNULL
valeurs.Si vous devez appeler cette requête souvent et que la table est en lecture seule, créez un
MATERIALIZED VIEW
.Point fin exotique: votre table est nommée
nostring
, mais semble contenir des hachages. En excluant les hachages au lieu des chaînes, il est possible que vous excluez plus de chaînes que prévu. Extrêmement improbable, mais possible.la source
Bienvenue sur DBA.SE!
Vous pouvez essayer de reformuler votre requête comme suit:
ou une autre possibilité:
NOT IN
est un puits typique de performances car il est difficile d'utiliser un index avec lui.Cela peut être encore amélioré avec des index. Un index sur
nostring.hash
semble utile. Mais d'abord: qu'obtenez-vous maintenant? (Il serait préférable de voir la sortie deEXPLAIN ANALYZE
puisque les coûts eux-mêmes ne disent pas le temps pris par les opérations.)la source
EXPLAIN ANALYZE
.Puisque le hachage est un md5, vous pouvez probablement essayer de le convertir en nombre: vous pouvez le stocker comme un nombre, ou simplement créer un index fonctionnel qui calcule ce nombre dans une fonction immuable.
D'autres personnes ont déjà créé une fonction pl / pgsql qui convertit (en partie) une valeur md5 du texte en chaîne. Voir /programming/9809381/hashing-a-string-to-a-numeric-value-in-postgressql pour un exemple
Je crois que vous passez vraiment beaucoup de temps à comparer des chaînes lors de la numérisation de l'index. Si vous parvenez à stocker cette valeur sous forme de nombre, cela devrait être vraiment vraiment plus rapide.
la source
Je rencontre beaucoup ce problème et découvre une astuce simple en 2 parties.
Créer un index de sous-chaîne sur la valeur de hachage: (7 est généralement une bonne longueur)
create index methods_idx_hash_substring ON methods(substring(hash,1,7))
Demandez à vos recherches / jointures d'inclure une correspondance de sous-chaîne, de sorte que le planificateur de requêtes est suggéré d'utiliser l'index:
vieux:
WHERE hash = :kwarg
Nouveau:
WHERE (hash = :kwarg) AND (substring(hash,1,7) = substring(:kwarg,1,7))
Vous devriez également avoir un index sur le brut
hash
.le résultat (généralement) est que le planificateur consultera d'abord l'indice de sous-chaîne et éliminera la plupart des lignes. puis il fait correspondre le hachage complet de 32 caractères à l'index (ou table) correspondant. cette approche a fait chuter les requêtes de 800 ms à 4 pour moi.
la source