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:
+-------------------+-------+--------------+------------+
| 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 % |
+-------------------+-------+--------------+------------+
+------------------+------------+--------------+------------+
| 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?
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:
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 ...
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.
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.