J'ai une grande table entities
avec environ 15 millions d'enregistrements. Je veux trouver les 5 premières lignes correspondant à «hockey» dans leur name
.
J'ai un index de texte intégral sur name
, qui est utilisé:gin_ix_entity_full_text_search_name
Requete:
SELECT "entities".*,
ts_rank(to_tsvector('english', "entities"."name"::text),
to_tsquery('english', 'hockey'::text)) AS "rank0.48661998202865475"
FROM "entities"
WHERE "entities"."place" = 'f'
AND (to_tsvector('english', "entities"."name"::text) @@ to_tsquery('english', 'hockey'::text))
ORDER BY "rank0.48661998202865475" DESC LIMIT 5
Durée 25,623 ms
Expliquez le plan 1 Limite (coût = 12666.89..12666.89 lignes = 5 largeur = 3116) 2 -> Trier (coût = 12666.89..12670.18 lignes = 6571 largeur = 3116) 3 Clé de tri: (ts_rank (to_tsvector ('english' :: regconfig, (name) :: text), '' 'hockey' '' :: tsquery)) 4 -> Bitmap Heap Scan sur les entités (coût = 124.06..12645.06 lignes = 6571 largeur = 3116) 5 Vérifiez à nouveau Cond: (to_tsvector ('english' :: regconfig, (name) :: text) @@ '' 'hockey' '' :: tsquery) 6 Filtre: (PAS placé) 7 -> Scan d'index bitmap sur gin_ix_entity_full_text_search_name (coût = 0,00..123,74 lignes = 6625 largeur = 0) 8 Index Cond: (to_tsvector ('english' :: regconfig, (name) :: text) @@ '' 'hockey' '' :: tsquery)
Je ne comprends pas pourquoi il vérifie la condition d'index deux fois. (Recherchez les étapes 4 et 7 du plan). Est-ce à cause de ma condition booléenne ( not place
)? Si oui, dois-je l'ajouter à mon index pour obtenir une requête très rapide? Ou la condition de tri la ralentit-elle?
EXPLAIN ANALYZE
production:
Limite (coût = 4447.28..4447.29 lignes = 5 largeur = 3116) (temps réel = 18509.274..18509.282 lignes = 5 boucles = 1) -> Trier (coût = 4447.28..4448.41 lignes = 2248 largeur = 3116) (temps réel = 18509.271..18509.273 lignes = 5 boucles = 1) Clé de tri: (ts_rank (to_tsvector ('english' :: regconfig, (name) :: text), '' 'test' '' :: tsquery)) Méthode de tri: top-N heapsort Mémoire: 19 Ko -> Bitmap Heap Scan sur les entités (coût = 43,31..4439,82 lignes = 2248 largeur = 3116) (temps réel = 119,003..18491,408 lignes = 2533 boucles = 1) Vérifiez à nouveau Cond: (to_tsvector ('english' :: regconfig, (name) :: text) @@ '' 'test' '' :: tsquery) Filtre: (PAS placer) -> Bitmap Index Scan sur gin_ix_entity_full_text_search_name (coût = 0,00..43,20 lignes = 2266 largeur = 0) (temps réel = 74,093..74,093 lignes = 2593 boucles = 1) Index Cond: (to_tsvector ('english' :: regconfig, (name) :: text) @@ '' 'test' '' :: tsquery) Durée totale: 18509,381 ms
Voici mes paramètres DB. Il est hébergé par Heroku, sur les services Amazon. Ils le décrivent comme ayant 1,7 Go de RAM, 1 unité de traitement et une base de données de 1 To maximum.
nom | paramètre actuel ------------------------------ + ------------------- -------------------------------------------------- ------------------------------------ version | PostgreSQL 9.0.7 sur i486-pc-linux-gnu, compilé par GCC gcc-4.4.real (Ubuntu 4.4.3-4ubuntu5) 4.4.3, 32 bits archive_command | test -f /etc/postgresql/9.0/main/wal-ed/ARCHIVING_OFF || envdir /etc/postgresql/9.0/resource29857_heroku_com/wal-ed/env wal-e wal-push% p archive_mode | sur archive_timeout | 1 minute checkpoint_completion_target | 0,7 checkpoint_segments | 40 client_min_messages | remarquer cpu_index_tuple_cost | 0,001 cpu_operator_cost | 0,0005 cpu_tuple_cost | 0,003 effective_cache_size | 1530000kB hot_standby | sur lc_collate | en_US.UTF-8 lc_ctype | en_US.UTF-8 listen_addresses | * log_checkpoints | sur log_destination | syslog log_line_prefix | % u [JAUNE] log_min_duration_statement | 50ms log_min_messages | remarquer logging_collector | sur maintenance_work_mem | 64 Mo max_connections | 500 max_prepared_transactions | 500 max_stack_depth | 2 Mo max_standby_archive_delay | -1 max_standby_streaming_delay | -1 max_wal_senders | dix port | random_page_cost | 2 encodage_serveur | UTF8 shared_buffers | 415MB ssl | sur syslog_ident | resource29857_heroku_com TimeZone | UTC wal_buffers | 8 Mo wal_keep_segments | 127 wal_level | pose chaude work_mem | 100 Mo (39 rangées)
ÉDITER
On dirait que ORDER BY
c'est la partie lente:
d6ifslbf0ugpu=> EXPLAIN ANALYZE SELECT "entities"."name",
ts_rank(to_tsvector('english', "entities"."name"::text),
to_tsquery('english', 'banana'::text)) AS "rank0.48661998202865475"
FROM "entities"
WHERE (to_tsvector('english', "entities"."name"::text) @@ to_tsquery('english', 'banana'::text))
LIMIT 5;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------------
Limit (cost=43.31..53.07 rows=5 width=24) (actual time=76.583..103.623 rows=5 loops=1)
-> Bitmap Heap Scan on entities (cost=43.31..4467.60 rows=2266 width=24) (actual time=76.581..103.613 rows=5 loops=1)
Recheck Cond: (to_tsvector('english'::regconfig, (name)::text) @@ '''banana'''::tsquery)
-> Bitmap Index Scan on gin_ix_entity_full_text_search_name (cost=0.00..43.20 rows=2266 width=0) (actual time=53.592..53.592 rows=1495 loops=1)
Index Cond: (to_tsvector('english'::regconfig, (name)::text) @@ '''banana'''::tsquery)
Total runtime: 103.680 ms
Contre. avec ORDER BY
:
d6ifslbf0ugpu=> EXPLAIN ANALYZE SELECT "entities"."name",
ts_rank(to_tsvector('english', "entities"."name"::text),
to_tsquery('english', 'banana'::text)) AS "rank0.48661998202865475"
FROM "entities"
WHERE (to_tsvector('english', "entities"."name"::text) @@ to_tsquery('english', 'banana'::text))
ORDER BY "rank0.48661998202865475" DESC
LIMIT 5;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------------------------
Limit (cost=4475.12..4475.13 rows=5 width=24) (actual time=15013.735..15013.741 rows=5 loops=1)
-> Sort (cost=4475.12..4476.26 rows=2266 width=24) (actual time=15013.732..15013.735 rows=5 loops=1)
Sort Key: (ts_rank(to_tsvector('english'::regconfig, (name)::text), '''banana'''::tsquery))
Sort Method: top-N heapsort Memory: 17kB
-> Bitmap Heap Scan on entities (cost=43.31..4467.60 rows=2266 width=24) (actual time=0.872..15006.763 rows=1495 loops=1)
Recheck Cond: (to_tsvector('english'::regconfig, (name)::text) @@ '''banana'''::tsquery)
-> Bitmap Index Scan on gin_ix_entity_full_text_search_name (cost=0.00..43.20 rows=2266 width=0) (actual time=0.549..0.549 rows=1495 loops=1)
Index Cond: (to_tsvector('english'::regconfig, (name)::text) @@ '''banana'''::tsquery)
Total runtime: 15013.805 ms
Mais je ne comprends toujours pas pourquoi c'est plus lent. On dirait qu'il récupère la même quantité de lignes à partir de Bitmap Heap Scan, mais cela prend beaucoup plus de temps?
Réponses:
Le tri des lignes coûtera quelque chose est évident. Mais pourquoi autant?
Sans
ORDER BY rank0...
Postgres, on peut justets_rank()
pour seulement 5 lignes.ts_rank()
pour chacun d'eux.ORDER BY name
simplement de voir le coût de calculto_tsquery('english', 'hockey'::text))
pour les lignes superflues et combien il reste pour récupérer plus de lignes et trier.la source
Une analyse bitmap fonctionne comme ceci: L'index est analysé pour trouver les emplacements des tuples correspondant aux conditions d'index. Le bitmap peut passer par une combinaison logique avec les résultats d'autres analyses bitmap, en utilisant une logique booléenne sur les bits. Ensuite, les pages contenant les données sont accessibles dans l'ordre des numéros de page, pour réduire l'accès au disque et, espérons-le, transformer certaines lectures aléatoires en lectures séquentielles.
Un scan d'index bitmap peut avoir besoin de devenir "avec perte" pour tenir en mémoire - il réduit sa précision du niveau de tuple au niveau de la page. Si vous augmentez work_mem (au moins pour cette seule requête), vous pouvez éviter cela. Il ne pourra pas ignorer l'analyse de tas de bitmap sur la ligne 4 ou le filtre sur la ligne 6, mais il pourra peut-être ignorer la nouvelle vérification sur la ligne 5.
la source
Étant donné que la question modifiée donne l'impression que le but est de choisir quelques-unes des meilleures correspondances, plutôt qu'une liste de tout ce qui correspond, je poste une réponse distincte pour cela, car c'est un problème assez différent.
Vous voudrez peut-être envisager un index KNN - GiST (pour K Nearest Neighbour - Generalized Search Tree), qui peut être extrait directement de l'index par ordre de similitude - vous n'avez donc pas besoin de lire au hasard tous ces tuples de tas et de les trier les descendre pour trouver les K meilleurs matchs.
À ce jour, je ne pense pas que quiconque ait implémenté le support KNN-GIST pour les requêtes tsearch (même si j'ai été assuré que cela peut être fait, c'est juste une question de quelqu'un qui prend le temps de le faire), mais peut-être le support du trigramme (qui est fait) fonctionnera pour votre application. La principale différence est que les recherches de trigrammes n'utilisent pas de dictionnaires pour la racine et les synonymes comme le fait tsearch; vous ne trouverez que des correspondances exactes de mots.
Pour essayer des trigrammes pour votre exemple, vous voudrez probablement indexer "nom" comme ceci:
Ensuite, vous pouvez rechercher comme ceci:
Notez les opérateurs utilisés et l'
ORDER BY
utilisation de l'alias de la colonne "similarité". Je ne m'éloignerais pas trop de ce schéma en l'essayant. L'index sur le tsvector n'est pas utilisé pour cette recherche.À moins de problèmes avec votre configuration actuelle (qui pourraient facilement jeter toute votre machine virtuelle dans une pagination désespérée à partir d'une surcharge de mémoire), vous aimerez probablement vraiment les performances de cela. Je ne sais pas s'il a le comportement que vous voulez.
la source