web-dev-qa-db-fra.com

Postgres multiple joint une requête lente, comment stocker l'enregistrement enfant par défaut

Ive a obtenu une petite base de données Postgres, environ 10.000 enregistrements stockant les clients de l'entreprise.
J'ai une question d'effectuer " lent " (environ une demi-seconde) qui est réalisée très souvent, et mon patron me veut l'améliorer.

Tout d'abord - mon code:

select customer_alias.id, customer_alias.name, site.address, phone.phonenumber
from customer_alias
join customer on customer_alias.customer_id = customer.id
left join site on customer.default_site_id = site.id
left join contact_phonenumbers as phone on site.default_phonenumber = phone.id

(Edited left join customer à join customer)

Ce qui saute pour moi, je suis avec une jointure à customer même si je ne suis pas rien sélectionner de ce dossier. J'ai actuellement à se joindre à elle pour obtenir le default_site_id, Une clé étrangère à la table site.

Chaque client peut avoir plusieurs sites, mais un seul doit être affiché dans cette liste (un client doit être ouvert pour voir tous les sites). Donc, ma question est, si je ne peux pas optimiser la requête, est-il une autre façon que je peux stocker un site par défaut pour un client particulier? La même chose vaut pour le défaut n ° de téléphone

Un client peut avoir de nombreux sites, mais un site n'a qu'un seul client (plusieurs à un).


rendement EXPLAIN:

Hash Join  (cost=522.72..943.76 rows=5018 width=53)
  Hash Cond: (customer.id = customer_alias.customer_id)
  ->  Hash Right Join  (cost=371.81..698.77 rows=5018 width=32)
        Hash Cond: (site.id = customer.default_site_id)
        ->  Hash Right Join  (cost=184.91..417.77 rows=5018 width=32)
              Hash Cond: (phone.id = site.default_phonenumber)
              ->  Seq Scan on contact_phonenumbers phone  (cost=0.00..121.70 rows=6970 width=17)
              ->  Hash  (cost=122.18..122.18 rows=5018 width=23)
                    ->  Seq Scan on site  (cost=0.00..122.18 rows=5018 width=23)
        ->  Hash  (cost=124.18..124.18 rows=5018 width=8)
              ->  Seq Scan on customer  (cost=0.00..124.18 rows=5018 width=8)
  ->  Hash  (cost=88.18..88.18 rows=5018 width=29)
        ->  Seq Scan on customer_alias  (cost=0.00..88.18 rows=5018 width=29)

EXPLAIN ANALYZE Retour:

Hash Join  (cost=522.72..943.76 rows=5018 width=53) (actual time=12.457..26.655 rows=5018 loops=1)
  Hash Cond: (customer.id = customer_alias.customer_id)
  ->  Hash Right Join  (cost=371.81..698.77 rows=5018 width=32) (actual time=8.589..18.796 rows=5018 loops=1)
        Hash Cond: (site.id = customer.default_site_id)
        ->  Hash Right Join  (cost=184.91..417.77 rows=5018 width=32) (actual time=4.499..11.067 rows=5018 loops=1)
              Hash Cond: (phone.id = site.default_phonenumber)
              ->  Seq Scan on contact_phonenumbers phone  (cost=0.00..121.70 rows=6970 width=17) (actual time=0.007..1.581 rows=6970 loops=1)
              ->  Hash  (cost=122.18..122.18 rows=5018 width=23) (actual time=4.465..4.465 rows=5018 loops=1)
                    Buckets: 1024  Batches: 1  Memory Usage: 277kB
                    ->  Seq Scan on site  (cost=0.00..122.18 rows=5018 width=23) (actual time=0.007..2.383 rows=5018 loops=1)
        ->  Hash  (cost=124.18..124.18 rows=5018 width=8) (actual time=4.072..4.072 rows=5018 loops=1)
              Buckets: 1024  Batches: 1  Memory Usage: 197kB
              ->  Seq Scan on customer  (cost=0.00..124.18 rows=5018 width=8) (actual time=0.009..2.270 rows=5018 loops=1)
  ->  Hash  (cost=88.18..88.18 rows=5018 width=29) (actual time=3.855..3.855 rows=5018 loops=1)
        Buckets: 1024  Batches: 1  Memory Usage: 309kB
        ->  Seq Scan on customer_alias  (cost=0.00..88.18 rows=5018 width=29) (actual time=0.008..1.664 rows=5018 loops=1)
Total runtime: 27.290 ms"

Structures de table

 
/* -------------------------------------- ------------------------------------- 
 * Tableau: contacts.customer 
 * ---------------------------------------------- --------------------------- */
 CREATE TABLE contacts.customer (
 id NOT NULL SERIE, 
 nom entier, - clé étrangère à contacter.Customer_alias 
 par défaut_site_id Entreger, - Touche étrangère à contacter.Site [.____] - 12 autres champs non liés à la requête [.____] Contrainte Customer_pKey Touche principale (ID) 
 Contrainte par défaut_site_id Touche étranger (Default_site_id) 
 Références Contact.Site (ID) Correspondre à la mise à jour Aucune action sur SUPPRIMER Aucune action; 
) [.____] [.____] - circulaire clé étrangère à customer_alias ajoutée plus tard [.____] [.____] - Répertorié:..... id, nom, default_site_id (btree) [.____] 
 
/* -------------------------------------- -------------------------------------- 
 * Tableau: co ntacts.customer_alias 
 * ----------------------------------------- --------------------------------- */
 CREATE TABLE contacts.customer_alias (
 id NOT NULL SERIE, [.____] customer_id entier, [.____] nom text, [.____] -.... 5 autres champs (non utilisés dans la requête) [.____] CONTRAINTE customer_alias_pkey PRIMARY KEY (id) , 
 Contrainte Compte_alias_customer_id_fkey Touche étrangère (Customer_id) 
 Références Contact.Comment (ID) Correspondre à la mise à jour Aucune action sur Delete Aucune action différée initialement différée initialement [.____]) 
 - Indexé: ID, Customer_ID (BTREE) [.____] 
 [.____] - Customer_alias Clé étrangère [.____] ALTER TABLE CONTACTS.PLUSTOMATEUR [. ____] nom) 
 RÉFÉRENCES contacts.customer_alias (id) MATCH SIMPLE 
 MISE À JOUR SUR L'ACTION SUR LE NON SUPPRIMER AUCUNE ACTION; [. .____] [. .____] [. .____]/* --- ----------------------------------------- ------------------------------- 
 * Tableau: contacts.site [. .____] * - -------------------------------------------------- --------------------- */
 CREATE TABLE contacts.site (
 id NOT NULL SERIE, 
 customer_id entier, 
 texte d'adresse 
 default_contact_id entier, 
 entier default_phonenumber, 
 - 9 autres domaines non apparentés [. .____] clé primaire site_pkey CONTRAINTE (id), 
 FOREIGN KEY de CONTRAINTE (de customer_id) 
 RÉFÉRENCES contacts.customer (id) SIMPLE MATCH 
 ON UPDATE ACTION NO NO ACTION SUPPRIMER 
) 
 indexé: id, customer_id, default_contact_id, default_phonenumber (btree) [. .____] [. .____] [. .____]/* --------------------- -------------------------------------------------- ---- 
 * Tableau: contacts.contact_phonenumbers [. .____] * ----------------------------- -------------------------------------------- */
 Créer des contacts de table.Contact_phonenumbers ([.____] ID Serial Not NULL, 
 SITE_ID INTEGER, [.____] Texte de phonénumber, [.____] - 4 autres champs non liés [.____] CONTRAINTE contact_phonenumbers_pkey PRIMARY KEY (id) 
) 
 - indexé: id, site_id (btree) [. .____]

Si je lance le côté client de requête, via ODBC, il faut compter entre 450-500 millisecondes. Si je lance la requête dans pgAdmin III, il déclare la requête prend environ 250 millisecondes, bien que parfois il faut 60-100ms (qui est ce que je vise).
Je n'ai pas accès SSH au serveur, donc je ne peux pas courir directement.

Je considère seulement 100 de ces lignes à tout moment à l'écran, est-il possible de récupérer uniquement les lignes pertinentes? J'ai essayé de limiter le résultat par exemple LIMIT 100, OFFSET 2345, Mais qui effectue une nouvelle recherche chaque fois.

Merci beaucoup pour l'aide à ce jour!

5
berkeleybross

Vous écrivez:

Chaque client peut avoir plusieurs sites, mais un seul doit être affiché dans cette liste.

Pourtant, votre requête récupère toutes les lignes. Ce serait un point d'optimiser. Mais vous ne définissez pas non plus ce que site doit être cueilli.

De toute façon, cela n'aimable pas beaucoup ici. Votre EXPLAIN montre seulement 5026 lignes pour la numérisation site (5018 pour la numérisation customer). Donc, presque tout client n'a réellement plus d'un site. Avez-vous ANALYZE vos tables avant de courir EXPLAIN?

Des chiffres que je vois dans votre EXPLAIN, index ne vous donnera rien pour cette requête. Les analyses de la table séquentielle seront la solution la plus rapide possible. Une demi-seconde est plutôt lente pour 5000 rangées, cependant. Peut-être que votre base de données nécessite un réglage général ?

Peut-être que la requête elle-même est plus rapide, mais "une demi-seconde" inclut le transfert de réseau? Expliquez Analyser nous en dire plus.

Si cette requête est votre goulot d'étranglement, je vous suggère de mettre en œuvre un Vue matérialisée .


Après avoir fourni plus d'informations, je constate que mon diagnostic tient à peu près.

La requête elle-même a besoin de 27 ms. Pas beaucoup de problème là-bas. "Une demi-seconde" était le genre de malentendu que j'avais soupçonné. La partie lente est le transfert de réseau (plus codage/décodage de SSH, éventuellement rendu). Vous ne devriez que récupérer 100 rangées, cela permettrait résoudre la majeure partie, même si cela signifie exécuter toute la requête à chaque fois.

Si vous allez la route avec une vue matérialisée comme je l'ai proposé, vous pouvez ajouter un Numéro de série sans lacunes à l'index de la table plus sur celui-ci - en ajoutant une colonne row_number() OVER (<your sort citeria here>) AS mv_id.

Ensuite, vous pouvez interroger:

SELECT *
FROM   materialized_view
WHERE  mv_id >= 2700
AND    mv_id <  2800;

Cela fonctionnera très rapide. LIMIT/OFFSET _ ne peut pas concourir, cela doit calculer toute la table avant de pouvoir trier et choisir 100 rangées.


pgadmin timing

Lorsque vous exécutez une requête de l'outil de requête, le volet de message montre quelque chose comme:

Total query runtime: 62 ms.

Et la ligne d'état indique le même temps. Je cite Pgadmin Aide à ce sujet:

La ligne d'état indiquera combien de temps la dernière requête a pris pour compléter. Si un jeu de données a été renvoyé, non seulement l'heure écoulée pour l'exécution du serveur est affichée, mais également le temps de récupérer les données du serveur à la page de sortie de données.

Si vous souhaitez voir le temps sur le serveur, vous devez utiliser SQL EXPLAIN ANALYZE ou la construction Shift + F7Beyboard raccourci ou Query -> Explain analyze. Ensuite, au bas de la sortie Explique, vous obtenez quelque chose comme ceci:

Total runtime: 0.269 ms
11
Erwin Brandstetter