IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Requêtes MySQL Discussion :

Problème d'index ignoré sur des intervalles de dates


Sujet :

Requêtes MySQL

  1. #1
    Membre actif
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2006
    Messages
    70
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Février 2006
    Messages : 70
    Points : 218
    Points
    218
    Par défaut Problème d'index ignoré sur des intervalles de dates
    Bonjour,

    Je viens solliciter votre aide, car il me semble que MySQL ne tiens pas compte de la valeur "date" de mon index composite, lorsque je l'utilise avec un intervale.

    Mon problème fait suite aux difficultés de mises en place de cette fameuse table:
    http://www.developpez.net/forums/d13...itation-table/


    Il s'agit d'une table d'historique qui sauvegarde l'état de chaque variable d'un appareil.
    Ces différents types de variables possibles sont enregistré dans une table à part:

    Code SQL : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    VALEUR
    (
            ValeurId        INT               NOT NULL,
            ValeurNom       VARCHAR(64)       NOT NULL
    )
            PRIMARY KEY (ValeurId) ;
    Il existe 339 types de valeurs possibles.


    Et voici la table qui sauvegarde les valeurs:

    Code SQL : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
    APPAREIL_VALEUR
    (
            AppareilId        INT               NOT NULL,
            ValeurId          INT               NOT NULL,
            ValeurDate        TIMESTAMP         NOT NULL,
            ValeurString      VARCHAR(32)       NOT NULL,
            ValeurWrite       TINYINT(1)        NOT NULL,
     
            PRIMARY KEY (AppareilId, ValeurId, ValeurDate), 
            FOREIGN KEY (AppareilId) REFERENCES APPAREIL, 
            FOREIGN KEY (ValeurId) REFERENCES VALEUR
    );


    En plus de l'index créé par la clef primaire (AppareilId, ValeurId, ValeurDate), un deuxième index existe pour gérer les fameuses valeurs en mode écriture:
    (AppareilId, ValeurWrite, ValeurId, ValeurDate)




    Et comme un mauvais dessin vaut mieux qu'un long discourt, je remet également un exemple type de jeu de données à sauvegarder:

    +----------------------+--------------+------------------+-------------+
    | ValeurNom            | ValeurString | ValeurDate       | ValeurWrite |
    +----------------------+--------------+------------------+-------------+
    | Consigne à atteindre |           20 | 15/04/2013 18:30 |         oui |
    | Sonde 1              |           15 | 15/04/2013 18:15 |         non |
    | Consigne à atteindre |           15 | 15/04/2013 18:00 |         non |
    | Sonde 2              |          300 | 15/04/2013 17:00 |         non |
    | Erreur sonde 2       |          oui | 15/04/2013 17:00 |         non |
    | Nom de la sonde 1    |         Toto | 15/04/2013 17:00 |         oui |
    | ...                  |              |                  |             |
    +----------------------+--------------+------------------+-------------+
    

    Le problème survient lorsqu'il est nécéssaire de récupérer un ensemble de valeurs entre deux dates.
    Par exemple, pour connaitre les valeurs moyenne, maxi et mini:

    Code SQL : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    SELECT AVG(ValeurString +0.0) as avg, MIN(ValeurString +0.0) AS min, MAX(ValeurString +0.0) AS max, v.ValeurNom
    FROM APPAREIL_VALEUR AS av
    INNER JOIN VALEUR AS v
    ON v.ValeurId = av.ValeurId
    WHERE av.AppareilId = 289
    AND av.ValeurWrite IS FALSE
    AND av.ValeurDate BETWEEN '2013-06-01 00:00:00' AND '2013-06-02 00:00:00'
    GROUP BY av.AppareilId, av.ValeurWrite, av.ValeurId

    (Les +0.0 ne sont pas très propres, mais permettent d'utiliser MIN et MAX sur les valeurs représentées, c'est à dire integer ou float, et non les string.)


    La table contient plus des 100 millions de valeurs.



    Cette requète, exécutée sur un ancien appareil (plus de 2 millions de valeurs à lui seul) peu prendre jusqu'à 40 secondes !

    Voici l'explain associé:

    +----+------------+-----------+--------+----------------------+-------------+-------------------------+----------+---------------------------------------+
    | id | select_type| table     | type   | possible_keys        | key         | key_len | ref           |     rows | Extra                                 |
    +----+------------+-----------+--------+----------------------+-------------+-------------------------+----------+---------------------------------------+
    |  1 | PRIMARY    | av        | range  | PRIMARY, idx_valeurs | idx_valeurs |       3 | NULL          |  2420378 | Using where                           |
    |  1 | PRIMARY    | v         | eq_ref | PRIMARY              | PRIMARY     |       4 | av.AppareilId |        1 |                                       |
    +----+------------+-----------+--------+----------------------+-------------+-------------------------+----------+---------------------------------------+
    
    La même requète, utilisé sur un appareil récent ramène le champ "rows" à quelques milliers, exécuté en quelques millisecondes.

    Celà me fait penser que la requète exécute un fullscan de la table.


    Si je modifie la requète de façon à ne plus utiliser BETWEEN mais une simple égalité, le résultat est radicalement différent:

    Code SQL : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    SELECT AVG(ValeurString +0.0) as avg, MIN(ValeurString +0.0) AS min, MAX(ValeurString +0.0) AS max, v.ValeurNom
    FROM APPAREIL_VALEUR AS av
    INNER JOIN VALEUR AS v
    ON v.ValeurId = av.ValeurId
    WHERE av.AppareilId = 289
    AND av.ValeurWrite IS FALSE
    AND av.ValeurDate = '2013-06-01 00:00:00'
    GROUP BY av.AppareilId, av.ValeurWrite, av.ValeurId

    +----+------------+-----------+--------+----------------------+-------------+----------------------------------+----------+----------------------------------------------+
    | id | select_type| table     | type   | possible_keys        | key         | key_len | ref                    |     rows | Extra                                        |
    +----+------------+-----------+--------+----------------------+-------------+----------------------------------+----------+----------------------------------------------+
    |  1 | PRIMARY    | v         | index  | PRIMARY              | ValeurNom   |      42 |                   NULL |      339 | Using index, Using temporary, Using filesort |
    |  1 | PRIMARY    | av        | range  | PRIMARY, idx_valeurs | PRIMARY     |      11 | const,v.ValeurId,const |        1 | Using where                                  |
    +----+------------+-----------+--------+----------------------+-------------+----------------------------------+----------+----------------------------------------------+
    
    Evidement, le résulat est vide, mais la requète s'exécute instantannément, et EXPLAIN permet d'observer qu'il ne sera pas nécéssaire de scanner toute la table pour ne rien retourner.



    D'ailleurs, un troisième test en utilisant un interval d'une seconde ramène le nombre de rows à 2420378 (l'explain est alors le même que celui du premier exemple)

    BETWEEN '2013-06-01 22:00:00' AND '2013-06-01 22:00:01'

    (Et tout ça pour sortir toujours un résultat vide... ironie...)




    Alors qu'est ce que j'ai loupé ?
    Il semble que l'index soit totalement ignoré pour gérer l'intervalle de valeurs demandées (car il est bien utilisé pour ne sélectionner que les valeurs de l'appareil recherché).

    Est-ce une mauvaise utilisation ? Une limitation de MySQL ?

  2. #2
    Expert confirmé
    Homme Profil pro
    Inscrit en
    Mai 2002
    Messages
    3 173
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France, Rhône (Rhône Alpes)

    Informations forums :
    Inscription : Mai 2002
    Messages : 3 173
    Points : 5 345
    Points
    5 345
    Par défaut
    Bonjour,


    Dans votre 1ere requete il n'y a pas de fullscan de table effectué.

    Lisez ceci: http://dev.mysql.com/doc/refman/5.0/...ra-information

    Si dans votre index vous inversez les colonnes ValeurId et ValeurDate est-ce cela va plus vite ?

  3. #3
    Membre actif
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2006
    Messages
    70
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Février 2006
    Messages : 70
    Points : 218
    Points
    218
    Par défaut
    Merci d'avoir pris le temps de vous pencher sur mon cas.


    Parler de "fullscan" était peut être un peu extrême.
    J'entendais par là que le moteur lisait tous les enregistrement pour un appareil (soit 2 millions pour l'exemple).

    J'ai donc rajouté un index, identique au premier à l'exception de l'inversion de ValeurId et ValeurDate.
    (AppareilId, ValeurWrite, ValeurDate, ValeurId)


    Effectivement, c'est beaucoup mieux:

    +----+-------------+-----------+------+----------------------+---------+----------------------------------+----------+---------------------------------+
    | id | select_type | table     | type | possible_keys        | key     | key_len | ref                    |     rows | Extra                           |
    +----+-------------+-----------+------+----------------------+---------+----------------------------------+----------+---------------------------------+
    |  1 | SIMPLE      | v         | ALL  | PRIMARY              | NULL    |    NULL |                   NULL |      339 | Using temporary; Using filesort |
    |  1 | SIMPLE      | av        | ref  | PRIMARY, ....        | PRIMARY |       7 |       const,v.ValeurId |       78 | Using where                     |
    +----+-------------+-----------+------+----------------------+---------+----------------------------------+----------+---------------------------------+
    

    Mais je ne comprends pas pourquoi le moteur n'est pas capable d'optimiser la recherche sur le premier index.

    Si celui fonctionne comme un arbre, les premières clauses WHERE AppareilId AND ValeurWrite, vont permettre d'en élaguer la plus grande partie.
    Arrive ensuite dans l'arbre les différents champs ValeurId, sur lesquels est posé une clause GROUP BY.
    Le moteur regroupe donc les noeuds restants de l'index (les ValeurDate) par ValeurID. Il "suffit" alors de faire un range pour chaque ValeurID.

    Mais apparement, il ne procède pas ainsi puisqu'il va lire toutes les entrées de ValeurDate !

    Créer un index semble être une solution, mais la redondance me gène un peu... N'y a t'il pas moyen de faire autrement ?

  4. #4
    Membre actif
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2006
    Messages
    70
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Février 2006
    Messages : 70
    Points : 218
    Points
    218
    Par défaut
    J'avance toujours à taton sur ce problème, et je ne comprends pas comment MySQL procède pour utiliser ou non l'index.

    Je disais dans mon dernier post que la création d'un index semblait améliorer la requète. Mais celà n'est vrai que si la clause INNER JOIN est présente:

    Voici les deux requètes avec l'EXPLAIN associé:

    Code SQL : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    SELECT AVG(ValeurString +0.0) AS avg, MIN(ValeurString +0.0) AS min, MAX(ValeurString +0.0) AS max, v.ValeurNom
    FROM APPAREIL_VALEUR AS av
    INNER JOIN VALEUR AS v
    ON v.ValeurId = av.ValeurId
    WHERE av.AppareilId = 289
    AND av.ValeurWrite IS FALSE
    AND av.ValeurDate BETWEEN '2013-06-01 00:00:00' AND '2013-06-02 00:00:00'
    GROUP BY av.AppareilId, av.ValeurWrite, av.ValeurId

    +----+-------------+-----------+------+----------------------+---------+----------------------------------+----------+---------------------------------+
    | id | select_type | table     | type | possible_keys        | key     | key_len | ref                    |     rows | Extra                           |
    +----+-------------+-----------+------+----------------------+---------+----------------------------------+----------+---------------------------------+
    |  1 | SIMPLE      | v         | ALL  | PRIMARY              | NULL    |    NULL |                   NULL |      339 | Using temporary; Using filesort |
    |  1 | SIMPLE      | av        | ref  | PRIMARY, ....        | PRIMARY |       7 |       const,v.ValeurId |       78 | Using where                     |
    +----+-------------+-----------+------+----------------------+---------+----------------------------------+----------+---------------------------------+
    

    Et la deuxième:

    Code SQL : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    SELECT AVG(ValeurString +0.0) AS avg, MIN(ValeurString +0.0) AS min, MAX(ValeurString +0.0) AS max, av.ValeurId
    FROM APPAREIL_VALEUR AS av
    WHERE av.AppareilId = 289
    AND av.ValeurWrite IS FALSE
    AND av.ValeurDate BETWEEN '2013-06-01 00:00:00' AND '2013-06-02 00:00:00'
    GROUP BY av.AppareilId, av.ValeurWrite, av.ValeurId

    +----+-------------+-----------+------+----------------------+---------+-----------------+----------+----------------------------------------------+
    | id | select_type | table     | type | possible_keys        | key     | key_len | ref   |     rows | Extra                                        |
    +----+-------------+-----------+------+----------------------+---------+-----------------+----------+----------------------------------------------+
    |  1 | SIMPLE      | av        | ref  | PRIMARY, ....        | index2  |       3 | const |  2420378 | Using where, Using temporary; Using filesort |
    +----+-------------+-----------+------+----------------------+---------+---------+-------+----------+----------------------------------------------+
    
    (Où "index2" est posé sur les colonnes AppareilId, ValeurWrite, ValeurDate, ValeurId )

    Cette deuxième requête ne devrait pourtant poser aucun problème ! Pourquoi donc l'index n'est il pas utilisé ?

  5. #5
    Expert confirmé
    Homme Profil pro
    Inscrit en
    Mai 2002
    Messages
    3 173
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France, Rhône (Rhône Alpes)

    Informations forums :
    Inscription : Mai 2002
    Messages : 3 173
    Points : 5 345
    Points
    5 345
    Par défaut
    En fait,

    c'est quand il utilise l'index que la requête devient lente.

    Sur le 1er explain on peut voir que MySql n'utilise pas l'index, mais passe par la clef primaire de la table APPAREIL_VALEUR pour réaliser sa jointure.
    Ceci après avoir fait un fullScan de la table valeur.


    Pour la 2eme requête, MySql utilise bien l'index que vous avez créé.


    Par contre je ne saurai dire le "pourquoi"

  6. #6
    Membre actif
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2006
    Messages
    70
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Février 2006
    Messages : 70
    Points : 218
    Points
    218
    Par défaut
    Les temps de réponses lors de l'exécution de la requêtes ne laissent cependant pas de doutes.

    Dans le premier cas, explain montre que la requète a théoriquement besoin de lire 78 lignes de la table. Et la requète s'exécute en quelques centaines de millisecondes (jamais plus d'une seconde sur tous les test effectués).


    Dans le deuxième cas, où explain indique que 2420378 lignes devront être analysées, l'exécution prends entre 3 et 7 secondes.


    Mais le pire c'est que dans le premier explain (celui avec 78 lignes lues) l'index2 semble pris en compte ! En effet, sur une table ne disposant pas de cet index, la valeur row remonte à 2420378.


    Bref, c'est incompréhensible à mon niveau...



    J'ai fait quelques test sur PostgreSQL, sur la même structure (mais avec beaucoup moins de valeur, car je n'ai pu importer le contenu de la table MySQL). Et là l'explain affiche un nombre de lignes théorique lu très faible, sur toutes mes requètes, même sans l'index2.


    A ce stade, je mettrais en doute la capacité de MySQL à utiliser les index de manière optimale. Mais n'étant pas expert, ce ne sont que des suppositions.
    Peut être que MySQL nécessite une manière spécifique d'écrire la requête pour pouvoir l'optimiser...

  7. #7
    Rédacteur

    Avatar de SQLpro
    Homme Profil pro
    Expert bases de données / SQL / MS SQL Server / Postgresql
    Inscrit en
    Mai 2002
    Messages
    21 848
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Expert bases de données / SQL / MS SQL Server / Postgresql
    Secteur : Conseil

    Informations forums :
    Inscription : Mai 2002
    Messages : 21 848
    Points : 52 964
    Points
    52 964
    Billets dans le blog
    6
    Par défaut
    Citation Envoyé par Sebajuste Voir le message
    A ce stade, je mettrais en doute la capacité de MySQL à utiliser les index de manière optimale. Mais n'étant pas expert, ce ne sont que des suppositions.
    Peut être que MySQL nécessite une manière spécifique d'écrire la requête pour pouvoir l'optimiser...
    L'optimiseur de MySQL est à peu près du niveau du brouillon de l'optimiseur de la première version d'Oracle.... Bref, c'est assez nu, dès que :
    1) il y a de nombreux index
    2) il y a de multiples jointures
    3) les clauses SELECT comporte de multiples colonnes ou expression...

    Ce ne sont pas les seules horreur de ce ersatz de SGBD Relationnel qu'est MySQL. A lire : http://blog.developpez.com/sqlpro/p9...oudre_aux_yeux

    A +
    Frédéric Brouard - SQLpro - ARCHITECTE DE DONNÉES - expert SGBDR et langage SQL
    Le site sur les SGBD relationnels et le langage SQL: http://sqlpro.developpez.com/
    Blog SQL, SQL Server, SGBDR : http://blog.developpez.com/sqlpro
    Expert Microsoft SQL Server - M.V.P. (Most valuable Professional) MS Corp.
    Entreprise SQL SPOT : modélisation, conseils, audit, optimisation, formation...
    * * * * * Expertise SQL Server : http://mssqlserver.fr/ * * * * *

  8. #8
    Expert confirmé
    Profil pro
    Inscrit en
    Août 2008
    Messages
    2 949
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Août 2008
    Messages : 2 949
    Points : 5 848
    Points
    5 848
    Par défaut
    Cette requète, exécutée sur un ancien appareil (plus de 2 millions de valeurs à lui seul) peu prendre jusqu'à 40 secondes !
    ...
    La même requète, utilisé sur un appareil récent ramène le champ "rows" à quelques milliers, exécuté en quelques millisecondes.
    Quelle est la version exacte de mysql sur les 2 serveurs ?
    Mysql 5.6 apporte des améliorations sur l'optimiseur :
    http://www.developpez.com/actu/51565...dministration/

  9. #9
    Membre actif
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2006
    Messages
    70
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Février 2006
    Messages : 70
    Points : 218
    Points
    218
    Par défaut
    Ouh lala !! J'ai aggro SQLpro comme diraient certains

    Bon, et bien j'en profite (tant qu'à faire) pour vous remercier de vos nombreux articles !

    Concernant celui de "la poudre aux yeux", je l'avais également déjà lu.
    Mais dans mon petit esprit naif et trop optimiste, j'osais espérer ne pas rencontrer de limitations critiques...

    Mais avec tous les déboirs que nous avons déjà eux, ça fait un moment que je milite pour changer de SGBDR.

    Çà me fait un argument de plus...


    On en vient à maudire celui qui a décidé "et si on mettais MySQL ?". Comme par hazard, il n'est plus dans l'équipe depuis belle lurette :p

Discussions similaires

  1. Réponses: 6
    Dernier message: 01/06/2015, 20h46
  2. Réponses: 3
    Dernier message: 04/04/2013, 21h02
  3. Problème sur des intervalles
    Par jamibt dans le forum C
    Réponses: 2
    Dernier message: 11/05/2011, 23h23
  4. Requêtes sur des intervalles de dates
    Par Tidus159 dans le forum Langage SQL
    Réponses: 4
    Dernier message: 09/05/2011, 16h43
  5. Réponses: 9
    Dernier message: 17/01/2004, 10h51

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo