Variations des performances des requêtes PostgreSQL LIKE


112

J'ai vu une assez grande variation dans les temps de réponse concernant les LIKErequêtes à une table particulière dans ma base de données. Parfois, j'obtiens des résultats dans un délai de 200 à 400 ms (très acceptable), mais d'autres fois, cela peut prendre jusqu'à 30 secondes pour renvoyer les résultats.

Je comprends que les LIKErequêtes nécessitent beaucoup de ressources, mais je ne comprends tout simplement pas pourquoi il y aurait une si grande différence dans les temps de réponse. J'ai construit un index btree sur le owner1terrain mais je ne pense pas que cela aide avec les LIKErequêtes. Quelqu'un a des idées?

Exemple de SQL:

SELECT gid, owner1 FORM parcels
WHERE owner1 ILIKE '%someones name%' LIMIT 10

J'ai aussi essayé:

SELECT gid, owner1 FROM parcels
WHERE lower(owner1) LIKE lower('%someones name%') LIMIT 10

Et:

SELECT gid, owner1 FROM parcels
WHERE lower(owner1) LIKE lower('someones name%') LIMIT 10

Avec des résultats similaires.
Nombre de lignes du tableau: environ 95 000.

Réponses:


282

FTS ne prend pas en charge LIKE

La réponse précédemment acceptée était incorrecte. La recherche de texte intégral avec ses index de texte intégral n'est pas du tout pour l' LIKEopérateur, elle a ses propres opérateurs et ne fonctionne pas pour les chaînes arbitraires. Il fonctionne sur des mots basés sur des dictionnaires et des souches. Il prend en charge la correspondance de préfixe pour les mots , mais pas avec l' LIKEopérateur:

Index trigrammes pour LIKE

Installez le module supplémentaire pg_trgmqui fournit des classes d'opérateurs pour les index GIN et GiST trigrammes pour soutenir tous LIKEet ILIKEmodèles , non seulement ceux de gauche ancrés:

Exemple d'index:

CREATE INDEX tbl_col_gin_trgm_idx  ON tbl USING gin  (col gin_trgm_ops);

Ou:

CREATE INDEX tbl_col_gist_trgm_idx ON tbl USING gist (col gist_trgm_ops);

Exemple de requête:

SELECT * FROM tbl WHERE col LIKE '%foo%';   -- leading wildcard
SELECT * FROM tbl WHERE col ILIKE '%foo%';  -- works case insensitively as well

Trigrammes? Qu'en est-il des cordes plus courtes?

Les mots avec moins de 3 lettres dans les valeurs indexées fonctionnent toujours. Le manuel:

Chaque mot est considéré comme ayant deux espaces préfixés et un espace suffixé lors de la détermination de l'ensemble de trigrammes contenus dans la chaîne.

Et les modèles de recherche avec moins de 3 lettres? Le manuel:

Pour les recherches LIKEet les recherches d'expressions régulières, gardez à l'esprit qu'un modèle sans trigrammes extractibles dégénérera en une analyse d'index complet.

Cela signifie que les analyses d'index / bitmap fonctionnent toujours (les plans de requête pour l'instruction préparée ne seront pas interrompus), cela ne vous offrira tout simplement pas de meilleures performances. En règle générale, pas de grosse perte, car les chaînes de 1 ou 2 lettres ne sont guère sélectives (plus de quelques pour cent des correspondances de table sous-jacentes) et la prise en charge de l'index n'améliorerait pas les performances au départ, car une analyse complète de la table est plus rapide.


text_pattern_ops pour la correspondance de préfixe

Pour les modèles uniquement ancrés à gauche (pas de caractère générique en tête), vous obtenez l'optimum avec une classe d'opérateur appropriée pour un index btree: text_pattern_opsou varchar_pattern_ops. Les deux fonctionnalités intégrées de Postgres standard, aucun module supplémentaire n'est nécessaire. Performances similaires, mais indice beaucoup plus petit.

Exemple d'index:

CREATE INDEX tbl_col_text_pattern_ops_idx ON tbl(col text_pattern_ops);

Exemple de requête:

SELECT * FROM tbl WHERE col LIKE 'foo%';  -- no leading wildcard

Ou , si vous devez exécuter votre base de données avec la locale 'C' (effectivement pas de locale), alors tout est de toute façon trié selon l'ordre des octets et un simple index btree avec la classe d'opérateur par défaut fait le travail.

Plus de détails, d'explications, d'exemples et de liens dans ces réponses connexes sur dba.SE:


En l'absence de caractère générique en tête sur une table de 500 000 lignes, l'index gin avec gin_trgm_ops semble être 10 fois plus rapide que btree
Nicolas

@nicolas: La comparaison dépend de nombreuses variables. Longueur de clé, répartition des données, longueur du motif, scan d'index possible uniquement ... Et surtout: version Postgres. Les indices GIN ont été considérablement améliorés aux pages 9.4 et 9.5. Une nouvelle version de pg_trgm (à paraître avec pg 9.6) va apporter plus d'améliorations.
Erwin Brandstetter

1
Si j'ai bien compris les documents, pg_trgmvous avez besoin d'une chaîne de requête d'au moins 3 caractères, par exemple, fo%ne pas frapper l'index mais effectuer une analyse à la place. Quelque chose à noter.
Tuukka Mustonen

1
@TuukkaMustonen: Bon point. Eh bien, les analyses d'index (bitmap) fonctionnent toujours , elles ne vous offriront tout simplement pas de meilleures performances. J'ai ajouté quelques précisions ci-dessus.
Erwin Brandstetter

7

Peut-être que les plus rapides sont des modèles ancrés avec une sensibilité à la casse comme celle qui peut utiliser des index. c'est-à-dire qu'il n'y a pas de caractère générique au début de la chaîne de correspondance afin que l'exécuteur puisse utiliser une analyse de plage d'index. ( le commentaire pertinent dans la documentation est ici ) Lower et ilike perdront également votre capacité à utiliser l'index à moins que vous ne créiez spécifiquement un index à cette fin (voir les index fonctionnels ).

Si vous souhaitez rechercher une chaîne au milieu du champ, vous devez examiner les index de texte intégral ou de trigrammes . Le premier est dans le noyau de Postgres, l'autre est disponible dans les modules contrib.


Je n'avais pas pensé à créer un index sur la valeur minuscule du champ. De cette façon, je peux convertir le texte de la requête en minuscules sur le backend avant d'interroger.
Jason

4

Vous pouvez installer Wildspeed , un autre type d'index dans PostgreSQL. Wildspeed fonctionne avec les caractères génériques% word%, pas de problème. L'inconvénient est la taille de l'indice, cela peut être grand, très grand.


3

Veuillez exécuter la requête mentionnée ci-dessous pour améliorer les performances de la requête LIKE dans postgresql. créez un index comme celui-ci pour des tables plus grandes:

CREATE INDEX <indexname> ON <tablename> USING btree (<fieldname> text_pattern_ops)

Cela ne fonctionne que si le modèle ne commence pas par un caractère générique - dans ce cas, les deux premiers exemples de requêtes commencent tous les deux par un caractère générique.
cbz


1

J'ai récemment eu un problème similaire avec une table contenant 200000 enregistrements et je dois faire des requêtes LIKE répétées. Dans mon cas, la chaîne recherchée a été corrigée. D'autres domaines variaient. Parce que ça, j'ai pu réécrire:

SELECT owner1 FROM parcels
WHERE lower(owner1) LIKE lower('%someones name%');

comme

CREATE INDEX ix_parcels ON parcels(position(lower('someones name') in lower(owner1)));

SELECT owner1 FROM parcels
WHERE position(lower('someones name') in lower(owner1)) > 0;

J'ai été ravi lorsque les requêtes sont revenues rapidement et que j'ai vérifié que l'index était utilisé avec EXPLAIN ANALYZE:

 Bitmap Heap Scan on parcels  (cost=7.66..25.59 rows=453 width=32) (actual time=0.006..0.006 rows=0 loops=1)
   Recheck Cond: ("position"(lower(owner1), 'someones name'::text) > 0)
   ->  Bitmap Index Scan on ix_parcels  (cost=0.00..7.55 rows=453 width=0) (actual time=0.004..0.004 rows=0 loops=1)
         Index Cond: ("position"(lower(owner1), 'someones name'::text) > 0)
 Planning time: 0.075 ms
 Execution time: 0.025 ms

0

Vos requêtes similaires ne peuvent probablement pas utiliser les index que vous avez créés car:

1) vos critères LIKE commencent par un caractère générique.

2) vous avez utilisé une fonction avec vos critères LIKE.


0

Chaque fois que vous utilisez une clause sur une colonne avec des fonctions par exemple LIKE, ILIKE, supérieur, inférieur, etc. Alors postgres ne prendra pas en considération votre index normal. Il effectuera une analyse complète de la table en passant par chaque ligne et sera donc lent.

La bonne façon serait de créer un nouvel index en fonction de votre requête. Par exemple, si je veux faire correspondre une colonne sans respect de la casse et ma colonne est un varchar. Ensuite, vous pouvez le faire comme ça.

create index ix_tblname_col_upper on tblname (UPPER(col) varchar_pattern_ops);

De même, si votre colonne est un texte, vous faites quelque chose comme ça

create index ix_tblname_col_upper on tblname (UPPER(col) text_pattern_ops);

De même, vous pouvez changer la fonction supérieure en toute autre fonction de votre choix.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.