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

SQL Procédural MySQL Discussion :

Fonction et vitesse d'exécution ?


Sujet :

SQL Procédural MySQL

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre confirmé
    Profil pro
    Inscrit en
    Octobre 2010
    Messages
    163
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Octobre 2010
    Messages : 163
    Par défaut Fonction et vitesse d'exécution ?
    Bonjour à vous,

    Je m'interroge sur une chose concernant la vitesse d'exécution dans le cas où on utilise des fonctions par rapport à si on ne les utilisait pas. Par exemple, imaginez que je fasse une fonction dont le but serait de retourner une valeur nommée "get_info_table2" :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    BEGIN
    DECLARE result VARCHAR(64);
    set result = "";
         SELECT table2.type
         INTO result
         FROM table2 
         WHERE table2.id=_id
         ORDER BY table2.timestamp DESC LIMIT 1;
    RETURN result;
    END
    Maintenant, j'aimerais savoir si faire :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    SELECT table.id, table.nom, get_info_table2(table.id) as info
    FROM table
    WHERE *
    serait plus rapide, plus lent, ou équivalent à
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    SELECT table.id, table.nom, 
    (SELECT table2.type
         INTO result
         FROM table2 
         WHERE table2.id=table.id
         ORDER BY table2.timestamp DESC LIMIT 1) as info
    FROM table
    WHERE *
    Bon je sais qu'en l'occurence, l'exemple est un peu con et qu'il serait sans doute plus élégant de faire une jointure mais c'est la vitesse d'exécution d'une fonction que j'aimerais discuter ici... En d'autres termes, ma question est : "créer une fonction et utiliser son nom dans une requête est-il plus rapide que de mettre le code de la fonction directement dans la requête qui l'utilise ??"

    L'avantage que je vois à créer une fonction est que la "maintenance" est plus facile : si ce bloc est utilisé dans plusieurs requêtes et qu'il doit être modifié par la suite, il me suffira de le modifier une seule fois (dans la déclaration de fonction) pour que le changement se répercute partout bien sûr... Mais si c'est calamiteux en terme de performances, ce n'est peut-être pas idéal non plus...

    Merci pour vos retours d'expérience(s) et vos avis ! :-)

    RL

  2. #2
    Rédacteur

    Avatar de SQLpro
    Homme Profil pro
    Expert bases de données / SQL / MS SQL Server / Postgresql
    Inscrit en
    Mai 2002
    Messages
    21 992
    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 992
    Billets dans le blog
    6
    Par défaut
    Les bases de données relationnelles procède par manipulation d'ensemble de données. Les fonction, procédures et autres routines procèdent par itération.
    Les manipulation ensemblistes relèguent la notion d'ordre ce qui permet :
    • d'utiliser des index (seek - recherche) plutôt que d'effectuer des lectures ligne à ligne (scan - balayage)
    • de multithreader une même opération (groupage, tri, balayage...) alors que l'itération qui suppose un ordre oblige à un traitement monothread.
    • de simplifier le code de la requête (algèbre relationnelle : factorisation par exemple) ce qui permet de ne faire que quelques opérations lorsque le résultat est déterministe à la place de toutes les opérations
    • d'optimiser la requête en choisissant l'ordre d'exécution des opérations et non l'ordre d'écriture

    En mode itératif (et les fonctions le sont) aucune de ces manipulation n'est supportable.
    Ce sont quelques uns des multiples avantages des bon SGBD Relationnels. Mais quand je parle des bons, je ne cite pas MySQL !
    A me lire : https://blog.developpez.com/sqlpro/p...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/ * * * * *

  3. #3
    Membre confirmé
    Profil pro
    Inscrit en
    Octobre 2010
    Messages
    163
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Octobre 2010
    Messages : 163
    Par défaut
    Hmmmm... grand merci pour votre réponse mais sans doute que mes connaissances ne me permettent pas de la comprendre... je ne vois pas comment déduire la reponse à ma question de votre intervention... pourriez-vous m'aiguiller un peu plus svp? Mille mercis encore...

  4. #4
    Rédacteur

    Avatar de SQLpro
    Homme Profil pro
    Expert bases de données / SQL / MS SQL Server / Postgresql
    Inscrit en
    Mai 2002
    Messages
    21 992
    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 992
    Billets dans le blog
    6
    Par défaut
    En gros si j'ai une table composée de 100 000 000 de lignes dont 99 999 999 ont comme valeur de la colonne C = 321 et une seule avec la valeur 1, et que je veut faire la requête SELECT suivante :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    SELECT *, LOG(C)
    FROM   MaTable
    Si le serveur, s'il dispose d'un index, il ne fera que 2 opérations :
    • LOG(1) dont le résultat est reporté sur l'unique ligne avec la valeur 1
    • LOG(321) dont le résultat est reporté sur toutes les autres lignes.



    Si vous utilisez une fonction utilisateur pour ce faire, alors il sera obligé d'exécuter 100 000 000 calculs !

    Autrement dit, la vitesse d'exécution pour ce cas de figure est simple... Si le calcul dure 1ms alors dans le premier cas la réponse intervient en 2 ms et dans le second au bout de 27h 45m 40s... !

    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/ * * * * *

  5. #5
    Membre confirmé
    Profil pro
    Inscrit en
    Octobre 2010
    Messages
    163
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Octobre 2010
    Messages : 163
    Par défaut
    Merci pour votre réponse ; je comprends mieux mais pas encore bien... Tout à fait naïvement, je me disais à vous lire que l'index sur la colonne C permettait de n'évaluer la fonction LOG que sur les valeurs distinctes et de reporter le résultat là où il faut (donc de n'exécuter le calcul de LOG(1) puis LOG(321) qu'une seule fois dans votre exemple). Du coup, que ce soit la fonction LOG ou une autre fonction utilisateur, je me disais que cela n'avait pas grande importance, grâce à l'index :-) Mais vous semblez dire que si on utilise une fonction utilisateur même en ayant un index, le calcul sera stupidement fait à chaque fois.
    Du coup je ne comprends plus bien votre réponse : qu'est-ce que l'index et le fait que ce soit une fonction utilisateur (et pas une fonction MySQL) ont à voir l'un avec l'autre ??

  6. #6
    Rédacteur

    Avatar de SQLpro
    Homme Profil pro
    Expert bases de données / SQL / MS SQL Server / Postgresql
    Inscrit en
    Mai 2002
    Messages
    21 992
    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 992
    Billets dans le blog
    6
    Par défaut
    Un SGBDR établis un plan de requête, en lisant le texte de la requête (transformé en arbre algébrique relationnel) et décide des opérations à effectuer par rapport aux évaluations de cardinalité (nombre de ligne à manipuler) effectuées sur des distributions statistiques. Tout ceci sans exécuter quoi que ce soit, puisque le but est de trouver comment exécuter le mieux possible les traitements inhérents à la requête.
    Pour avoir des statistiques il faut des données. Une table, un index contient des données et peut donc faire l'objet de statistiques permettant d'établir un plan de requête efficace car optimal. Une fonction ne contient rien... Elle ne peut donc pas être estimée d'aucune manière. Ainsi le plan établi sur une fonction est systématiquement de l'exécuter pour toutes les valeurs...

    Si vous voulez un exemple précis en voici un :

    -- soit la table :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    CREATE TABLE MaTable (C INT)
    -- insertion primale (10 lignes) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    INSERT INTO MaTable VALUES (321);
    INSERT INTO MaTable VALUES (321);
    INSERT INTO MaTable VALUES (321);
    INSERT INTO MaTable VALUES (321);
    INSERT INTO MaTable VALUES (321);
    INSERT INTO MaTable VALUES (321);
    INSERT INTO MaTable VALUES (321);
    INSERT INTO MaTable VALUES (321);
    INSERT INTO MaTable VALUES (321);
    INSERT INTO MaTable VALUES (321);
    --> exécutez 3 fois la requête suivante
    -- (ATTENTION c'est long surtout si vous avez : un SGBDR lent, un PC lent... !)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    INSERT INTO MaTable
    SELECT t1.C 
    FROM   MaTable AS t1
           CROSS JOIN MaTable AS t2;
    --> pour avoir 149 096 310 lignes dans la table :
    -- 10 x 10 + 10 = 110
    -- 110 x 110 + 110 = 12210
    -- 12210 x 12210 + 12210 = 149096310

    -- on ajoute une seule ligne avec la valeur 1
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    INSERT INTO MaTable VALUES (1);
    -- indexer 149 096 311 lignes, c'est long aussi !!!
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    CREATE INDEX X ON MaTable (C);
    -- on créé une deuxième table pour stocker des valeurs de logarithmes précalculés :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    CREATE TABLE T_LOG (N INT, LOGARITHME DECIMAL(16, 2));
    -- insérons deux lignes de logarithmes précalculés :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    INSERT INTO T_LOG VALUES (321, LOG(321)), (1, LOG(1));
    -- et indexons la table :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    CREATE INDEX Y ON T_LOG (N);
    -- requête itérative du fait de la fonction
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    SELECT * 
    FROM   MaTable 
    WHERE  LOG(C) = 0;
    -- requête ensembliste du fait de la jointure
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    SELECT * 
    FROM   MaTable AS T 
           JOIN T_LOG AS L 
    	        ON T.C = L.N 
    WHERE LOGARITHME = 0;
    À priori le calcul des plans de requête montre ceci (je suis sous SQL Server) :
    Nom : Comparaison_plan_de_requete.png
Affichages : 1028
Taille : 30,5 Ko

    Il semble que le première requête devrait être plus rapide...

    Testez les maintenant en exécution....

    Sur mon PC :
    Requête 1 : Temps UC = 24452*ms, temps écoulé = 12284*ms.
    Requête 2 : Temps UC = 0*ms, temps écoulé = 0*ms.

    CQFD...

    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/ * * * * *

  7. #7
    Membre prolifique Avatar de Artemus24
    Homme Profil pro
    Agent secret au service du président Ulysses S. Grant !
    Inscrit en
    Février 2011
    Messages
    6 787
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Agent secret au service du président Ulysses S. Grant !
    Secteur : Finance

    Informations forums :
    Inscription : Février 2011
    Messages : 6 787
    Par défaut
    Salut trucmuche2005.

    Je n'aime pas raisonner dans le vide sur des questions trop vague car il suffit d'un rien pour changer la réponse à une question donnée.

    Citation Envoyé par trucmuche2005
    Je m'interroge sur une chose concernant la vitesse d'exécution dans le cas où on utilise des fonctions par rapport à si on ne les utilisait pas.
    Avant de vous poser des questions métaphysiques, il serait bon d'analyser votre problème et ensuite de trouver la solution qui se prête le mieux à ce que vous cherchez à faire.

    Citation Envoyé par trucmuche2005
    créer une fonction et utiliser son nom dans une requête est-il plus rapide que de mettre le code de la fonction directement dans la requête qui l'utilise ??
    Cela dépend de ce que vous faites !

    Une fonction n'a pas de mémoire et donc si vous l'appelez 1 million de fois, elle va refaire le même calcul 1 million de fois.
    Cela peut être la réponse à votre attente, si vous devez faire un calcul qui dépend de cette ligne.
    Mais comme le dit SQLPRO, vous raisonnez séquentiellement (en mode itératif) et non ensemble de données. La différente se fait sentir en terme de performance !

    Analysons votre requête :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    SELECT    type
        INTO  result
        FROM  table2
       WHERE  id = _id
    ORDER BY  timestamp DESC LIMIT 1
    Votre requête n'est pas très optimisée. Pourquoi ? Je n'ai pas trop ce "limit 1" !
    D'après ce que j'ai pu comprendre, vous recherchez pour un "id" (je suppose passage en paramètre de votre fonction), le type, ayant le plus grand "timestamp".

    Si vous avez plusieurs lignes ayant le même identifiant, vous devez sélectionner la ligne ayant le plus grand "timestamp".

    Je soupçonne déjà un problème de modélisation. Vous recherchez un "type" associé à un "identifiant" donné.
    Si vous avez toujours le même "type" pour un "identifiant" donné, pourquoi le mettre dans cette table ?
    Vous allez créer des répétitions inutiles.

    Dans le cas où il y a plusieurs "type" pour un "identifiant" donné, pourquoi celui ayant le "timestamp" le plus grand serait le bon ?
    J'espère aussi que vous n'avez pas plusieurs "timestamp" identique pour un "identifiant" donné.

    Vous cherchez à récupérer le "type", mais rien n'indique qu'il est nécessairement unique !
    Le cas classique.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    SELECT    t1.type
        INTO  result
        FROM  table2 as t1
       WHERE  t1.timestamp = ( SELECT MAX(t2.timestamp) from result as t2 where t2.id = _id )
    GROUP BY  t1.id, t1.type
    Cette façon de faire est moins performante que la solution ci-après :

    La bonne façon de procéder.
    Il faut faire une jointure sur la même table.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    SELECT           t1.type
               INTO  result
               FROM  table2 as t1
    LEFT OUTER JOIN  table2 as t2
                 ON  t2.id = t1.id
                AND  t2.timestamp > t1.timestamp
              WHERE  t2.id is null;
    En terme de performance, cette solution est meilleure que la précédente.

    Poursuivons l'analyse.
    Vous avez deux tables, dont l'une est la mère (table1 )de l'autre (table2).
    Là encore, il vaut mieux faire une jointure entre vos deux tables. Ce qui donne :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    SELECT           t1.id,
                     t1.nom,
                     t2.type
               FROM  table1 as t1
         INNER JOIN  table2 as t2
                 ON  t2.id        = t1.id
     
    LEFT OUTER JOIN  table2 as t3
                 ON  t3.id        = t1.id
                AND  t3.timestamp > t2.timestamp
              WHERE  t3.id is null;
    En résumé, je donne entièrement raison à SQLPRO.
    Faire une fonction pour traiter séquentiellement ce que l'on peut faire de manière plus performante par l'approche ensemble de données est une grosse connerie !!!

    Citation Envoyé par trucmuche2005
    L'avantage que je vois à créer une fonction est que la "maintenance" est plus facile
    Quelle maintenance ? Sur des petites applications de ce genre, il n'y a pas de maintenance.
    Et si on doit intervenir, c'est plutôt pour faire une refonte de l'application.

    Vous croyez que la maintenance doit être privilégiée au détriment de la performance ? Encore une autre connerie.
    Si vous utilisez dans vos traitements cinquante fois la même requête (???), dans ce cas là, mettez votre requête dans un script dédié à cet effet et incluez ce script en substitution de votre requête.
    Ainsi, votre requête sera isolé à un seul endroit, ce qui facilite la maintenance si elle a lieu, même si elle est appelée cinquante fois.
    Hormis la connaissance des bases de données, j'ai aussi l'impression que vous ne connaissez pas grand chose aux techniques de développements.

    @+

  8. #8
    Modérateur

    Profil pro
    dba
    Inscrit en
    Janvier 2010
    Messages
    5 643
    Détails du profil
    Informations personnelles :
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : dba

    Informations forums :
    Inscription : Janvier 2010
    Messages : 5 643
    Par défaut
    Citation Envoyé par Artemus24 Voir le message
    Le cas classique.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    SELECT    t1.type
        INTO  result
        FROM  table2 as t1
       WHERE  t1.timestamp = ( SELECT MAX(t2.timestamp) from result as t2 where t2.id = _id )
    GROUP BY  t1.id, t1.type
    Cette façon de faire est moins performante que la solution ci-après :

    La bonne façon de procéder.
    Il faut faire une jointure sur la même table.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    SELECT           t1.type
               INTO  result
               FROM  table2 as t1
    LEFT OUTER JOIN  table2 as t2
                 ON  t2.id = t1.id
                AND  t2.timestamp > t1.timestamp
              WHERE  t2.id is null;
    En terme de performance, cette solution est meilleure que la précédente.
    C'est sacrément péremptoire, et ce sera pourtant faux dans bien des cas si les bon index sont posés. En fonction de la répartition des données, la solution 1 pourra être bien meilleure !

  9. #9
    Membre prolifique Avatar de Artemus24
    Homme Profil pro
    Agent secret au service du président Ulysses S. Grant !
    Inscrit en
    Février 2011
    Messages
    6 787
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Agent secret au service du président Ulysses S. Grant !
    Secteur : Finance

    Informations forums :
    Inscription : Février 2011
    Messages : 6 787
    Par défaut
    Salut aieeeuuuuu.

    Faites le test et vous vous rendrez compte par vous même que la seconde solution est plus rapide que la première.

    C'est même escartefigue qui m'a donné cette solution, il y a déjà fort longtemps.

    @+

  10. #10
    Membre confirmé
    Profil pro
    Inscrit en
    Octobre 2010
    Messages
    163
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Octobre 2010
    Messages : 163
    Par défaut
    Bonjour à vous,

    Et merci encore pour vos interventions.

    @ Artemus24 : même si votre compétence en la matière est indéniable par rapport à la mienne, je n'apprécie pas beaucoup votre ton relativement dénigrant et désagréable. Votre dernier message transpire la suffisance et je vous saurais gré de tolérer que d'autres sont là pour apprendre.
    Vous écrivez : "Vous croyez que la maintenance doit être privilégiée au détriment de la performance ? Encore une autre connerie." -- je n'ai jamais écrit ni pensé cela, justement... je pose la question justement pour allier les deux, vous n'avez pas compris cela ? Vous m'avez alors mal lu...
    Aussi, vous écrivez "Hormis la connaissance des bases de données, j'ai aussi l'impression que vous ne connaissez pas grand chose aux techniques de développements." -- De fait mais je n'ai jamais prétendu le contraire. J'apprends, petit à petit, et échanger avec des pros et des gens plus compétent fait partie de l'apprentissage.

    Constructivement maintenant, si vous le voulez bien.

    Vous dites : "Si vous utilisez dans vos traitements cinquante fois la même requête (???), dans ce cas là, mettez votre requête dans un script dédié à cet effet et incluez ce script en substitution de votre requête."
    C'est EXACTEMENT ma question initiale mais je cherche à rester dans MySQL et ma question est donc : "comment créer ce script dédié dont vous parlez en MySQL (cad sans sortir de MySQL) ??". À vous lire, je comprends que ce n'est pas en terme de fonction comme je le tentais (cfr mon premier post).

  11. #11
    Modérateur

    Profil pro
    dba
    Inscrit en
    Janvier 2010
    Messages
    5 643
    Détails du profil
    Informations personnelles :
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : dba

    Informations forums :
    Inscription : Janvier 2010
    Messages : 5 643
    Par défaut
    Citation Envoyé par Artemus24 Voir le message
    Faites le test ...
    OK
    Par contre je n'ai pas de MySQL sous la main et pas le temps (surtout pas l'envie en fait) de l'installer. Le test est donc fait sur SQL Server, je poste quand même le code, je vous laisserai l'adapter a MySQL.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
     
    CREATE TABLE Table2 (
    	id INT NOT NULL --PRIMARY KEY IDENTITY
    	,type  int
    	,timestamp DATETIME2(3)
    );
     
    INSERT INTO Table2 
    	SELECT TOP(50000) 1,1,'20170101'
    	FROM sys.objects A, sys.objects B, sys.objects C
    ;
    INSERT INTO Table2 
    	SELECT TOP(50000) 2,1,'20170101'
    	FROM sys.objects A, sys.objects B, sys.objects C
    ;
    INSERT INTO Table2 
    	SELECT TOP(50000) 3,1,'20170101'
    	FROM sys.objects A, sys.objects B, sys.objects C
    ;
     
    WITH T as (select row_number() OVER(partition by id order by id) as rn, timestamp from table2)
    	update T set timestamp = DATEADD(DAY, rn, timestamp)
    ;
     
    CREATE INDEX IX_TABLE2_ID_TS ON Table2(id, timestamp) INCLUDE (type);

    On a donc une table contenant 3 id distinct, avec chacun 50000 timestamp sans doublon.

    Pour la première requête (que j'ai corrigée) :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    SELECT    t1.type
        FROM  table2 as t1
       WHERE  t1.timestamp = ( SELECT MAX(t2.timestamp) from table2 as t2 where t2.id = t1.id )
    GROUP BY  t1.id, t1.type
    ;
    ==>
    Table 'Table2'. Nombre d'analyses 1, lectures logiques 543
    Temps UC = 16*ms, temps écoulé = 49*ms.
    
    et pour la seconde :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    SELECT           t1.type
               FROM  table2 as t1
    LEFT OUTER JOIN  table2 as t2
                 ON  t2.id = t1.id
                AND  t2.timestamp > t1.timestamp
              WHERE  t2.id is null;
    ==>
    Table 'Table2'. Nombre d'analyses 150001, lectures logiques 14148530
    Temps UC = 729382*ms, temps écoulé = 733753*ms.
    
    Citation Envoyé par Artemus24 Voir le message
    ...et vous vous rendrez compte par vous même que la seconde solution est plus rapide que la première.
    C'est même escartefigue qui m'a donné cette solution, il y a déjà fort longtemps.
    @+
    La solution 2 est donc ici 15000 fois plus lente, mais elle n'est pas forcément mauvaise en toute circonstances. Comme je le disais, la meilleure solution dépendra de la répartition des données.
    Dans mon test, j'ai volontairement mis peu d'id avec chacun beaucoup de timestamps. Mais si on inverse le ratio, la deuxième deviendra en effet plus efficace.

    Enfin tout ça nous éloigne un peu plus de la question initiale, qui est de savoir si il y avait une différence de performance entre utiliser une fonction ou mettre directement son contenu dans la requête.

  12. #12
    Membre confirmé
    Profil pro
    Inscrit en
    Octobre 2010
    Messages
    163
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Octobre 2010
    Messages : 163
    Par défaut
    Merci pour ce test, aieeuuuuu! Pour retrouver le cadre de ma question initiale, je serais curieux de connaître le temps d'exécution de la requête 1 (la plus rapide des deux tiennes) si tu mettais la sous-requête "SÉLECT" en fonction... aurais-tu encore la possibilité de faire ce test de ton côté ? Ce serait super sympa (et instructif). Je ferai le même sous mysql...

  13. #13
    Modérateur

    Profil pro
    dba
    Inscrit en
    Janvier 2010
    Messages
    5 643
    Détails du profil
    Informations personnelles :
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : dba

    Informations forums :
    Inscription : Janvier 2010
    Messages : 5 643
    Par défaut
    Pour rester comparable à ce qui est faisable sous MySQL, j'ai créé une fonction multi instructions :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    CREATE FUNCTION UDF_GET_MAX_TS(@id INT)
    RETURNS DATETIME2(3)
    WITH SCHEMABINDING
    AS
    BEGIN
    	DECLARE @result DATETIME2(3)
     
    	SELECT @Result = MAX(timestamp) from dbo.table2 where id = @id
     
    	RETURN @result
    END
    puis repris la requête 1 pour utiliser la fonction :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    SELECT    t1.type
        FROM  table2 as t1
    	WHERE  t1.timestamp = dbo.UDF_GET_MAX_TS(t1.id)
    GROUP BY  t1.id, t1.type
    ;
    ==>
    Table 'Worktable'. Nombre d'analyses 0, lectures logiques 0, lectures physiques 0, lectures anticipées 0, lectures logiques de données d'objets volumineux 0, lectures physiques de données d'objets volumineux 0, lectures anticipées de données d'objets volumineux 0.
    Table 'Workfile'. Nombre d'analyses 0, lectures logiques 0, lectures physiques 0, lectures anticipées 0, lectures logiques de données d'objets volumineux 0, lectures physiques de données d'objets volumineux 0, lectures anticipées de données d'objets volumineux 0.
    Table 'Table2'. Nombre d'analyses 1, lectures logiques 446, lectures physiques 0, lectures anticipées 0, lectures logiques de données d'objets volumineux 0, lectures physiques de données d'objets volumineux 0, lectures anticipées de données d'objets volumineux 0.
    
    , Temps UC = 8580*ms, temps écoulé = 8783*ms.
    
    On a donc bien une nette perte de performance.

    Mais sous SQL Server, on pourrait faire à la place une fonction table en ligne, et l'optimiseur peut alors faire son office :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    CREATE FUNCTION UDFIL_GET_MAX_TS(@id INT)	
    RETURNS TABLE 
    WITH SCHEMABINDING
    AS
    RETURN 
    (
    	SELECT  MAX(timestamp) AS MAX_TS from dbo.table2 where id = @id
    )
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    SELECT    t1.type
        FROM  table2 as t1
    	WHERE  t1.timestamp = (SELECT MAX_TS FROM dbo.UDFIL_GET_MAX_TS(id))
    GROUP BY  t1.id, t1.type
    ;
    ==>
    Table 'Table2'. Nombre d'analyses 1, lectures logiques 543, lectures physiques 0, lectures anticipées 0, lectures logiques de données d'objets volumineux 0, lectures physiques de données d'objets volumineux 0, lectures anticipées de données d'objets volumineux 0.
    , Temps UC = 31*ms, temps écoulé = 69*ms.
    
    Toutefois je doute que ce type de fonction existe sous MySQL.
    Par ailleurs, les fonctions multi-instructions empêchent le parallélisme sous SQL Server, mais la question ne se pose pas sous MySQL qui n'en fait pas de toute façon...

  14. #14
    Membre prolifique Avatar de Artemus24
    Homme Profil pro
    Agent secret au service du président Ulysses S. Grant !
    Inscrit en
    Février 2011
    Messages
    6 787
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Agent secret au service du président Ulysses S. Grant !
    Secteur : Finance

    Informations forums :
    Inscription : Février 2011
    Messages : 6 787
    Par défaut
    Salut à tous.

    Citation Envoyé par trucmuche2005
    J'apprends, petit à petit, et échanger avec des pros et des gens plus compétent fait partie de l'apprentissage.
    Developpez est un site consacré pour des professionnels et non pour des amateurs débutants.
    En venant ici, vous devez avoir le minimum requis, sinon nos échanges sont des pertes de temps.

    Une connerie n'est pas une insulte mais une erreur de raisonnement !

    Citation Envoyé par trucmuche2005
    C'est EXACTEMENT ma question initiale mais je cherche à rester dans MySQL
    Non, ce n'est pas votre question initiale. La voici, votre question initiale :
    Citation Envoyé par trucmuche2005
    Je m'interroge sur une chose concernant la vitesse d'exécution dans le cas où on utilise des fonctions par rapport à si on ne les utilisait pas.
    On vous a répondu que l'usage d'une fonction est beaucoup plus lente que l'approche ensembliste qui est la bonne approche.
    SQLPRO vous a démontré la différence, mais je pense que vous n'avez pas bien compris son message.

    Citation Envoyé par trucmuche2005
    et ma question est donc : "comment créer ce script dédié dont vous parlez en MySQL (cad sans sortir de MySQL) ??".
    En faisant des scripts mysql, vous avez la possibilité de mettre un "source nom_de_votre_script" pour faire l'équivalent d'un "include". Exemple :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    select * from `modele`;
     
    source test_1.sql
    Dans le script, j'appelle par la commande "source" un autre script qui va s'exécuter à la suite du "select".

    Citation Envoyé par trucmuche2005
    À vous lire, je comprends que ce n'est pas en terme de fonction comme je le tentais (cfr mon premier post).
    Vous avez pris le problème à l'envers. Vous avez pensé en premier à la maintenance et non à l'optimisation de votre requête.
    Peu importe que votre requête soit appelé 1 fois ou 50 fois. Elle doit être optimisée et c'est ce que vous dévez résoudre en premier.
    Vient ensuite la maintenance. La commande "source" est comme une fonction dans les langage de programmation, enfin je devrais plutôt dire comme un "include".
    Cela évite de répéter le même taitement et donc d'améliorer la maintenance et l'organisation de votre application.

    Citation Envoyé par aieeeuuuuu
    OK
    Par contre je n'ai pas de MySQL sous la main et pas le temps (surtout pas l'envie en fait) de l'installer.
    Donc votre démonstration ne sert à rien car ce qui est valable pour Microsoft SQL Server, ne l'est pas pour MySql et vice-versa.

    Citation Envoyé par aieeeuuuuu
    Comme je le disais, la meilleure solution dépendra de la répartition des données.
    Sur ce point, je vous rejoins.

    Mais quand j'ai fait le test, j'avais constaté que dans la première solution, MySql faisait cinq lectures, tandis que dans la seconde solution, MySql n'en faisait que trois.
    Le problème repose dans la façon dont MySql gère les lignes d'une table.
    Et nous savons tous que MySql n'est pas des plus performants !

    Il n'y a pas de règles générales concernant tous les SGBDR car chacun est développé selon des règles qui leurs sont propres.
    Ce qui est valable pour l'un, ne l'est pas pour un autre.
    Seul les tests permettrons de démontrer leur validité.

    Citation Envoyé par aieeeuuuuu
    Enfin tout ça nous éloigne un peu plus de la question initiale, qui est de savoir si il y avait une différence de performance entre utiliser une fonction ou mettre directement son contenu dans la requête.
    En ce qui me concerne, je rejoins SQLPRO où l'approche ensembliste est à privilégié par rapport à l'approche séquentielle.

    Citation Envoyé par aieeeuuuuu
    Toutefois je doute que ce type de fonction existe sous MySQL.
    Je confirme, cela n'existe pas sous MySql.

    Citation Envoyé par aieeeuuuuu
    Par ailleurs, les fonctions multi-instructions empêchent le parallélisme sous SQL Server, mais la question ne se pose pas sous MySQL qui n'en fait pas de toute façon...
    Le mutliprocessing est hors sujet en ce qui concerne la question initiale sous MySql.

    Merci quand même d'avoir fait le test !

    @+

  15. #15
    Membre confirmé
    Profil pro
    Inscrit en
    Octobre 2010
    Messages
    163
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Octobre 2010
    Messages : 163
    Par défaut
    Citation Envoyé par Artemus24 Voir le message
    Developpez est un site consacré pour des professionnels et non pour des amateurs débutants.
    En venant ici, vous devez avoir le minimum requis, sinon nos échanges sont des pertes de temps.
    Merci pour votre remarque, qui me surprend pas mal au vu du nombre de questions posées auxquelles je peux répondre en connaissant si peu... De plus, je pensais naïvement que le forum Developpez était un endroit où des moins connaisseurs pouvaient justement échanger avec des personnes plus expérimentées dans le but d'apprendre (un peu), à leur mesure. Bref, un lieu d'échange quoi. J'apprends maintenant que cela n'est pas le cas. Ah. Dans ce cas, pourriez-vous me conseiller un site où des débutants médiocres désireux d'apprendre comme moi pourraient échanger avec d'autres débutants médiocres en évitant de trop échanger avec des personnes compétentes pour éviter de leur faire perdre du temps ? Je m'empresserai de m'inscrire sur ce site... :-)
    Une chose m'interroge quand même sur vous, les gens compétents : pourquoi vous venez pour répondre à des questions ? Vous n'avez pas mieux à faire dites ? :-D Sérieusement...

    Alors sorry si ma question initiale était posée naïvement, incorrectement, et/ou était le reflet d'autres habitudes pas toujours très catholiques que j'ai voulu mimer dans mon début d'apprentissage de MySQL, je l'admets et tout cela se discute. Mais un peu de tolérance svp. Je n'ai ni l'ambition de développer un grand logiciel, ni l'ambition de devenir le développeur de l'année. Je suis juste curieux et je me pose des questions, rien de plus.
    Et puis si cela ne vous convient pas, rien ne vous oblige à perdre votre temps dans les topics que j'ouvre... Vous pouvez laisser les gens médiocres y répondre. Vous pouvez même éviter de les ouvrir, je ne vous en voudrai jamais :-D Bref, merci pour votre tolérance. Et un merci (vraiment sincère) à ceux qui auront fait autre chose dans ce topic que de dénigrer qui ou quoi que ce soit du haut de leur superbe et qui auront tenté de faire avancer le schmilblik, aussi élémentaire qu'il soit.

Discussions similaires

  1. vitesse d'exécution d'une fonction personalisée
    Par metaldan dans le forum Macros et VBA Excel
    Réponses: 4
    Dernier message: 08/04/2010, 18h27
  2. [VB.NET, CF 2.0, SQLite] Vitesse d'exécution d'une fonction
    Par agro dans le forum Windows Mobile
    Réponses: 1
    Dernier message: 16/04/2009, 13h07
  3. Réponses: 2
    Dernier message: 24/08/2006, 10h46
  4. Réponses: 4
    Dernier message: 02/04/2006, 18h42
  5. Créer une fonction mathématique pendant l'exécution
    Par zeprogrameur dans le forum Langage
    Réponses: 5
    Dernier message: 09/07/2004, 11h36

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