Bonjour,
Nous rencontrons un problème de performance sur une requête SQL de type "pour chaque type de valeur x retourne moi la plus proche inférieur d'une date y".
En effet, malgré l'utilisation d'un index qui me semble construit correctement, il semble que le moteur de requêtes ne puisse l'optimiser, et scan entièrement l'index (ou tout du moins, une partie conséquente).
Le contexte est le suivant: la base de données stock des valeurs d'appareils.
Les valeurs sont stockées dans la table T_DEVICE_VALUE
Les tables T_DEVICE et T_TYPE_VALUE ne sont pas fournies ici, car elle ne sont pas nécéssaires à la compréhension du problème. T_DEVICE contient la liste des appareils, et T_TYPE_VALUE la liste de tous les types de valeur (une liste de capteurs).
En production, la table T_DEVICE_VALUE contient près de 350 millions de valeurs, pour 400 type (T_TYPE_VALUE) de valeurs différents.
La clef primaire de la table T_DEVICE_VALUE étant composite, celle-ci sert également d'index. L'ordre me semble correct pour permettre des requètes "sargable".
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9 CREATE TABLE T_DEVICE_VALUE ( ID_DEVICE INTEGER NOT NULL, ID_TYPE INTEGER NOT NULL, VALUE_DATE TIMESTAMP NOT NULL, VAL CHARACTER VARYING(32) NOT NULL, PRIMARY KEY (ID_DEVICE, ID_TYPE, VALUE_DATE), FOREIGN KEY(ID_DEVICE) REFERENCES T_DEVICE(ID), FOREIGN KEY(ID_TYPE) REFERENCES T_TYPE_VALUE(ID) );
La requête initiale doit permettre de rechercher, pour une installation définie, le dernier état de toutes les valeurs à une date données.
Nous utilisons pour cela deux requêtes imbriquées. La première récupère dans l'index la liste des résultats formant les clef primaires (les couples ID_DEVICE, ID_TYPE, VALUE_DATE correspondant aux résultats souhautés), la seconde va récupérer dans la table la valeur associée pour chaque clef primaire.
Nous rencontrons des problèmes pour la première requête, celle devant récupérer toutes les clef primaires correspondant au résultat voulu.
Voici le résulat de l'explain analyse:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6 SELECT MAX( VALUE_DATE ), ID_DEVICE, ID_TYPE FROM T_DEVICE_VALUE WHERE ID_DEVICE = 1 AND VALUE_DATE <= '2015-01-10 07:00:00' GROUP BY ID_DEVICE, ID_TYPE
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6 HashAggregate (cost=832107.83..832107.84 rows=1 width=12) (actual time=58062.328..58062.392 rows=266 loops=1) -> Bitmap Heap Scan on t_device_value (cost=7760.43..830607.30 rows=300105 width=12) (actual time=673.857..57611.936 rows=991766 loops=1) Recheck Cond: ((id_device = 584) AND (value_date <= '2015-01-10 07:00:00'::timestamp without time zone)) -> Bitmap Index Scan on t_device_value_pkey (cost=0.00..7685.40 rows=300105 width=0) (actual time=589.813..589.813 rows=992466 loops=1) Index Cond: ((id_device = 584) AND (value_date <= '2015-01-10 07:00:00'::timestamp without time zone)) Total runtime: 58063.747 ms
J'interprète le résultat ainsi: même si la requête ne cherche que dans l'index, celui-ci est entièrement scanné. Ou tout du moins, puisque c'est un index Btree, toutes les valeurs de la profondeur de (id_type, value_date).
Par contre, je ne comprends pas pourquoi il fait la requête en deux temps, avec une revérification des contraintes de sélection.
Pour y voir plus clair, j'ai donc simplifié la requète pour rechercher un type précis:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5 SELECT MAX( VALUE_DATE ) FROM T_DEVICE_VALUE WHERE ID_DEVICE = 1 AND ID_TYPE = 1 AND VALUE_DATE <= '2015-01-10 07:00:00'
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7 Result (cost=3.65..3.66 rows=1 width=0) (actual time=0.109..0.109 rows=1 loops=1) InitPlan 1 (returns $0) -> Limit (cost=0.00..3.65 rows=1 width=8) (actual time=0.106..0.106 rows=1 loops=1) -> Index Only Scan Backward using t_device_value_pkey on t_device_value (cost=0.00..623.59 rows=171 width=8) (actual time=0.103..0.103 rows=1 loops=1) Index Cond: ((id_device = 584) AND (id_type = 50) AND (value_date IS NOT NULL) AND (value_date <= '2015-01-10 07:00:00'::timestamp without time zone)) Heap Fetches: 1 Total runtime: 0.165 ms
Cette fois, la requête est capable de rechercher correctement l'information, sans scan complet de l'index. On remarque l'utilisation du scan de l'index en "Backward".
Le temps de réponse me semble tout à fait correct pour une recherche aussi simple.
La même requête, qui retourne le même résultat, à la différence qu'un GROUP BY est ajouté:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6 SELECT MAX( VALUE_DATE ) FROM T_DEVICE_VALUE WHERE ID_DEVICE = 1 AND ID_TYPE = 1 AND VALUE_DATE <= '2015-01-10 07:00:00' GROUP BY ID_TYPE
Et là le temps de recherche explose de nouveau... Alors même que logiquement, le GROUP BY ne devrait avoir aucun effet.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5 GroupAggregate (cost=0.00..624.03 rows=1 width=12) (actual time=18.165..18.165 rows=0 loops=1) -> Index Only Scan using t_device_value_pkey on t_device_value (cost=0.00..623.16 rows=171 width=12) (actual time=18.161..18.161 rows=0 loops=1) Index Cond: ((id_device = 584) AND (id_type = 50) AND (value_date <= '2015-01-10 07:00:00'::timestamp without time zone)) Heap Fetches: 0 Total runtime: 18.235 ms
Je manque totalement de connaissances et d'expérience pour trouver une solution à ce problème.
Y a t'il un moyen d'améliorer la première requête présentée ici, afin d'avoir des performances decentes ?
Autrement, j'ai comme seule solution d'exécuter, coté application, 400 fois la deuxième requête pour réaliser manuellement le group by dans des performances acceptables. 400 * 0.165 ~= 66 ms
Même avec les allés retour SQL <=> application, ça sera toujours plus rapide. Mais qu'est ce que c'est moche...
Partager