Poser cette question, en particulier pour Postgres, car elle a un bon supoort pour les index R-tree / spatial.
Nous avons le tableau suivant avec une structure arborescente (modèle Nested Set) de mots et leurs fréquences:
lexikon
-------
_id integer PRIMARY KEY
word text
frequency integer
lset integer UNIQUE KEY
rset integer UNIQUE KEY
Et la requête:
SELECT word
FROM lexikon
WHERE lset BETWEEN @Low AND @High
ORDER BY frequency DESC
LIMIT @N
Je suppose qu'un indice de couverture sur (lset, frequency, word)
serait utile mais je pense qu'il peut ne pas bien fonctionner s'il y a trop de lset
valeurs dans la (@High, @Low)
plage.
Un simple index on (frequency DESC)
peut également parfois suffire, lorsqu'une recherche utilisant cet index donne tôt les @N
lignes qui correspondent à la condition de plage.
Mais il semble que les performances dépendent beaucoup des valeurs des paramètres.
Existe-t-il un moyen de le faire fonctionner rapidement, que la plage (@Low, @High)
soit large ou étroite et que les mots de fréquence supérieure soient heureusement dans la plage (étroite) sélectionnée?
Un arbre R / un indice spatial serait-il utile?
Ajouter des index, réécrire la requête, reconcevoir la table, il n'y a pas de limitation.
la source
lset,rset
etword
.Réponses:
Vous pourrez peut-être obtenir de meilleures performances en recherchant d'abord dans les rangées avec des fréquences plus élevées. Cela peut être réalisé en «granulant» les fréquences, puis en les parcourant de manière procédurale, par exemple comme suit:
-
lexikon
données testées et factices:granule
analyse (principalement pour l'information et le réglage):fonction de balayage des hautes fréquences en premier:
résultats (les timings doivent probablement être pris avec une pincée de sel mais chaque requête est exécutée deux fois pour contrer toute mise en cache)
en utilisant d'abord la fonction que nous avons écrite:
puis avec un simple scan d'index:
En fonction de vos données réelles, vous souhaiterez probablement faire varier le nombre de granules et la fonction utilisée pour y placer des lignes. La distribution réelle des fréquences est ici clé, tout comme les valeurs attendues pour la
limit
clause et la taille deslset
plages recherchées.la source
width_granule=8
entregranulae_start
et depuisgranulae_end
le niveau précédent?frequency
est généré: un grand écart entre 1e6 / 2 et 1e6 / 3, le numéro de ligne le plus élevé devient, le plus petit est. Quoi qu'il en soit, merci pour cette approche géniale !!Installer
Je m'appuie sur la configuration de @ Jack pour faciliter le suivi et la comparaison des utilisateurs. Testé avec PostgreSQL 9.1.4 .
A partir de là, je prends un itinéraire différent:
Table auxiliaire
Cette solution n'ajoute pas de colonnes à la table d'origine, elle a juste besoin d'une petite table auxiliaire. Je l'ai placé dans le schéma
public
, utilisez n'importe quel schéma de votre choix.Le tableau ressemble à ceci:
Comme la colonne
cond
va être utilisée dans SQL dynamique plus bas, vous devez faire ce tableau sécurisé . Toujours qualifier la table par schéma si vous ne pouvez pas être sûr d'un courant appropriésearch_path
et révoquer les privilèges d'écriture depublic
(et de tout autre rôle non approuvé):Le tableau
lex_freq
sert à trois fins:Index
Cette
DO
instruction crée tous les index nécessaires:Tous ces index partiels couvrent ensemble la table une fois. Ils ont à peu près la même taille qu'un index de base sur toute la table:
Jusqu'à présent, seuls 21 Mo d'index pour une table de 50 Mo.
Je crée la plupart des index partiels sur
(lset, frequency DESC)
. La deuxième colonne n'aide que dans des cas particuliers. Mais comme les deux colonnes impliquées sont de typeinteger
, en raison des spécificités de l' alignement des données en combinaison avec MAXALIGN dans PostgreSQL, la deuxième colonne n'agrandit pas l'index. C'est une petite victoire pour presque aucun coût.Cela ne sert à rien de le faire pour des index partiels qui ne couvrent qu'une seule fréquence. Ce sont juste sur
(lset)
. Les index créés ressemblent à ceci:Une fonction
La fonction est quelque peu similaire dans le style à la solution de @ Jack:
Différences clés:
SQL dynamique avec
RETURN QUERY EXECUTE
.Au fur et à mesure que nous parcourons les étapes, un plan de requête différent peut être bénéficiaire. Le plan de requête pour le SQL statique est généré une fois, puis réutilisé, ce qui peut économiser des frais généraux. Mais dans ce cas, la requête est simple et les valeurs sont très différentes. Dynamic SQL sera une grande victoire.
Dynamique
LIMIT
pour chaque étape de requête.Cela aide de plusieurs façons: Premièrement, les lignes ne sont extraites que si nécessaire. En combinaison avec SQL dynamique, cela peut également générer différents plans de requête pour commencer. Deuxièmement: pas besoin d'un supplément
LIMIT
dans l'appel de fonction pour couper le surplus.Référence
Installer
J'ai choisi quatre exemples et effectué trois tests différents avec chacun. J'ai pris le meilleur des cinq pour comparer avec le cache chaud:
La requête SQL brute du formulaire:
La même chose après la création de cet index
A besoin du même espace que tous mes index partiels ensemble:
La fonction
Résultats
1: Autonomie totale: 315,458 ms
2 : Autonomie totale: 36,458 ms
3: Autonomie totale: 0,330 ms
1: Autonomie totale: 294.819 ms
2 : Autonomie totale: 18.915 ms
3: Autonomie totale: 1.414 ms
1: Autonomie totale: 426.831 ms
2 : Autonomie totale: 217.874 ms
3: Autonomie totale: 1.611 ms
1: Durée totale d'exécution: 2458,205 ms
2 : Durée totale d'exécution: 2458,205 ms - pour de grandes plages de lset, le balayage séquentiel est plus rapide que l'index.
3: Durée d'exécution totale: 0,266 ms
Conclusion
Comme prévu, l'avantage de la fonction augmente avec des gammes plus grandes
lset
et plus petitesLIMIT
.Avec de très petites plages de
lset
, la requête brute en combinaison avec l'index est en fait plus rapide . Vous voudrez tester et peut-être branch: requête brute pour de petites plages delset
, sinon appel de fonction. Vous pouvez même simplement intégrer cela dans la fonction pour un "meilleur des deux mondes" - c'est ce que je ferais.En fonction de la distribution de vos données et des requêtes typiques, d'autres étapes
lex_freq
peuvent améliorer les performances. Testez pour trouver le bon endroit. Avec les outils présentés ici, cela devrait être facile à tester.la source
Je ne vois aucune raison d'inclure la colonne de mots dans l'index. Donc, cet indice
rendra votre requête rapide.
UPD
Il n'existe actuellement aucun moyen de créer un index de couverture dans PostgreSQL. Il y a eu une discussion sur cette fonctionnalité dans la liste de diffusion PostgreSQL http://archives.postgresql.org/pgsql-performance/2012-06/msg00114.php
la source
Utilisation d'un index GIST
Cela dépend de ce que vous voulez dire lorsque vous jeûnez: vous devez évidemment visiter chaque ligne de la plage car votre requête est
ORDER freq DESC
. Timide que le planificateur de requêtes couvre déjà cela si je comprends la question,Ici, nous créons un tableau avec 10k lignes de
(5::int,random()::double precision)
Nous l'indexons,
Nous l'interrogons,
Nous obtenons un
Seq Scan on t
. C'est tout simplement parce que nos estimations de sélectivité permettent à pg de conclure que l'accès au segment de mémoire est plus rapide que l'analyse d'un index et la nouvelle vérification. Donc, nous le rendons plus juteux en insérant 1 000 000 de lignes supplémentaires(42::int,random()::double precision)
qui ne correspondent pas à notre «gamme».Et puis on demande,
Vous pouvez voir ici que nous complétons en 4.6 MS avec un scan d'index uniquement ,
Élargir la plage pour inclure la table entière, produit un autre scan séquentiel - logiquement, et l'agrandir avec un autre milliard de lignes produirait un autre scan d'index.
Donc en résumé,
la source