web-dev-qa-db-fra.com

Performances de requête lente en raison du fichier temporaire?

Voici la requête:

SELECT "products".* 
FROM "products"
WHERE (status > 100) 
AND "products"."above_revenue_average" = 't' 
AND ("products"."category_id" NOT IN (5))
ORDER BY "products"."start_date" DESC 

J'ai un index sur status et start_date.

Chaque fois que je gère la requête de mon application, je reçois ce qui suit dans les journaux:

[WHITE] temporary file:
path "pg_tblspc/16386/PG_9.3_201306121/pgsql_tmp/pgsql_tmp2544.0", size 37093376 
Query: SELECT "products".* FROM "products"  WHERE (status > 100) 
AND "products"."above_revenue_average" = 't' 
AND ("products"."category_id" NOT IN (5))  ORDER BY "products"."start_date" DESC 

Je crois que cette création de fichiers temporaire est la cause des performances lentes.

Courir un EXPLAIN ANALYZE Je reçois les résultats suivants:

QUERY PLAN
 Sort  (cost=63395.28..63403.51 rows=16460 width=524)
       (actual time=524.134..562.635 rows=65294 loops=1)
   Sort Key: start_date
   Sort Method: external merge  Disk: 36224kB
   ->  Bitmap Heap Scan on products  
        (cost=4803.40..60389.73 rows=16460 width=524)
        (actual time=27.390..397.879 rows=65294 loops=1)
         Recheck Cond: (status > 100)
         Filter: (above_revenue_average AND (category_id <> 5))
         Rows Removed by Filter: 25115
         ->  Bitmap Index Scan on index_products_on_status
            (cost=0.00..4802.58 rows=89662 width=0) 
            (actual time=18.006..18.006 rows=90411 loops=1)
               Index Cond: (status > 100)
 Total runtime: 577.870 ms
(10 rows)

J'ai ensuite utilisé http://explain.depesz.com/ pour le rendre un peu plus lisible:

Par type de nœud statistiques

+-------------------+-------+--------------+------------+
|     node type     | count | sum of times | % of query |
+-------------------+-------+--------------+------------+
| Bitmap Heap Scan  |     1 | 379.873 ms   | 67.5 %     |
| Bitmap Index Scan |     1 | 18.006 ms    | 3.2 %      |
| Sort              |     1 | 164.756 ms   | 29.3 %     |
+-------------------+-------+--------------+------------+

Par table statistiques

+------------------+------------+--------------+------------+
|    Table name    | Scan count |  Total time  | % of query |
+------------------+------------+--------------+------------+
| scan type        | count      | sum of times | % of table |
| products         | 1          | 379.873 ms   | 67.5 %     |
| Bitmap Heap Scan | 1          | 379.873 ms   | 100.0 %    |
+------------------+------------+--------------+------------+

Puis-je augmenter la performance de la base de données en ajoutant d'autres index? Peut-être des composites? Des idées?

7
bnussey

work_mem

Évidemment, l'opération de tri se répand sur le disque:

Sort Method: external merge  Disk: 36224kB

Suite work_mem peut aider la requête, comme @ kassandry déjà suggérée . Augmentez le réglage jusqu'à ce que vous voyiez Memory au lieu de Disk dans la sortie EXPLAIN. Mais c'est probablement une mauvaise idée d'augmenter le réglage général basé sur une requête. Le paramètre correct dépend de la disponibilité RAM et de votre situation complète. Commencez par lire dans le Postgres Wiki .

Pour réparer votre requête, définissez work_mem assez haut pour votre transaction uniquement avec SET LOCAL Dans la même transaction la même transaction .

BEGIN;

SET LOCAL work_mem = '45MB';

SELECT ...

COMMIT;   -- or ROLLBACK

Vous avez probablement besoin d'un peu plus de 40 Mo. La représentation en mémoire est un peu plus grande que la représentation sur disque. En rapport:

Mettre en doute

Votre requête (après avoir coupé du bruit):

SELECT * 
FROM   products
WHERE  status > 100
AND    above_revenue_average  -- boolean can be used directly
AND    category_id <> 5
ORDER  BY start_date DESC;

Vos lignes sont une demi-kilobyte large (width=524). Avez-vous besoin de retourner toutes les colonnes? (Typiquement, vous ne le faites pas.) Seulement énumérer les colonnes de la liste SELECT dont vous avez besoin de votre requête pour améliorer les performances globales, d'autant plus que vous avez work_mem questions déjà.

L'une des colonnes impliquées peut-elle être nulle? Particulièrement important pour category_id et start_date. Vous voudrez peut-être vous adapter dans ce cas ...

Indice

Un indice multicolonné peut certainement aider les performances. (- comme @Paul décrit ). Vous devez peser des coûts et gagner. Si la performance de cette requête est importante ou si elle est très courante, allez-y. Ne créez pas un index spécial pour chaque requête. Aussi peu que possible, autant que nécessaire. Les index sont plus puissants lorsqu'ils sont partagés, qui augmente les chances que davantage d'entre eux restent dans le cache.

Une colonne boolean comme above_revenue_average est un candidat typique pour une condition dans un indice partiel plutôt que pour une colonne d'index.

Ma femme sauvage basée sur des informations incomplètes:

CREATE INDEX prod_special_idx ON products (start_date DESC)
WHERE  above_revenue_average
AND    status > 100
AND    category_id <> 5;

Utilisation DESC NULLS LAST Dans l'index et la requête si start_date peut être nul.

4
Erwin Brandstetter

Selon la manière dont les prédicats combinés sont sélectifs, j'imagine qu'un bon index pour cette requête particulière serait:

CREATE INDEX index_name
ON products
    (above_revenue_average ASC, start_date DESC)
WHERE
    status > 100
    AND category_id <> 5;

Le SELECT * est potentiellement problématique car l'indice ci-dessus ne contient pas toutes les colonnes. Si cela provoque la sélection de l'index de ne pas sélectionner le planificateur, vous pouvez d'abord récupérer uniquement les colonnes de touches primaires, puis rejoignez la table pour collecter les colonnes supplémentaires, pour les clés sélectionnées.

2
Paul White 9