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

Développement SQL Server Discussion :

Optimisation de script complexe (suppression de WHILE ?) [2008]


Sujet :

Développement SQL Server

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre Expert Avatar de Tober
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juillet 2007
    Messages
    824
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : Luxembourg

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Finance

    Informations forums :
    Inscription : Juillet 2007
    Messages : 824
    Par défaut Optimisation de script complexe (suppression de WHILE ?)
    Bonjour,

    J’ai un problème sur un logiciel comptable qui gère chaque dossier client comme une base de données.
    Il y a plusieurs choses dans ce logiciel que je gère de manière globale, c’est à dire que j’ai un dossier qui sert de template et que j’aimerai copier sur tous les autres clients.
    De plus, pour ne pas compliquer la chose, un dossier comptable a forcément des données annuelles et ce logiciel gère chaque année comptable sur une table différente.
    Exemple : La liste des écritures de l’année 2015 d’un client X sera une table ClientX..Historic01.

    Bien sur mon dossier template gère des cas pour l’année 2015 mais aussi d’autres cas pour d’autres années.

    Bref, pour réaliser les requêtes qui mettent à jour mes dossiers clients, je ne trouve pas d’autres moyens que de réaliser des boucles pour construire mes requêtes en chaînes de caractères et pour savoir quel dossier client affecter...

    Ce code marche mais je ne l'exécute pas pour plus d'une dizaine de dossier sinon ça prend beaucoup trop de temps.

    Code SQL : 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
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    declare @i int 
    declare @j int
    declare @Client varchar(500) 
     
    declare @numrows int 
    declare @numexo int
     
    declare @request as varchar(max) 
     
    declare @LogicielDB as varchar(max)
    set @LogicielDB = 'Client%'
     
    drop table ListeClientDB 
    create table ListeClientDB (idx int Primary Key IDENTITY(1,1),name varchar(100), nbExo int) 
     
    DECLARE @Logiciel_table TABLE (
        idx int Primary Key IDENTITY(1,1) 
        , Client_id nvarchar(128) ) 
     
    -- Remplit @Logiciel_table avec toutes les DBs concernées par mon logiciel.
    INSERT @Logiciel_table   
    select name from sys.databases where name like @LogicielDB
     
    -- Ici on parcours chaque DB pour en filtrer certain dossier sur base de règle métier...   
    SET @i = 1 
    SET @numrows = (SELECT COUNT(Client_id) FROM @Logiciel_table) 
    IF @numrows > 0 
        WHILE (@i <= @numrows)
        BEGIN 
     
            SET @Client = (SELECT Client_id FROM @Logiciel_table WHERE idx = @i) 
            select @request = 'insert into ListeClientDB select '''+@Client+''', 0 from ' + @Client + '.dbo.d_acctgeneral where generalid = ''112'' and wordingfr = ''Primes de fusion'''
            --select @request = @request + @Client
    		exec(@request) 
    	    --select(@request)
     
            -- increment counter for next Client
            SET @i = @i + 1 
        END 
     
    -- Ici, on compte le nombre d'exercice sur chaque dossier (nombre de table ''A_%AParam'' ou % est le numéro de l'année)
    SET @i = 1 
    SET @numrows = (SELECT COUNT(name) FROM ListeClientDB) 
    IF @numrows > 0 
        WHILE (@i <= @numrows) 
        BEGIN 
     
            SET @Client = (SELECT name FROM ListeClientDB WHERE idx = @i) 
            select @request = 'update ListeClientDB set nbExo = (select COUNT(T.name) from ' + @Client + '.sys.tables T where T.name like ''A_%AParam'') from ListeClientDB L where L.name = ''' + @Client + '''' 
                    exec(@request) 
            -- increment counter for next Year
            SET @i = @i + 1 
        END
     
    -- Affichage pour le suivi
    select * from ListeClientDB 
     
    -- Vidage puis copie des tables à copier du template
    SET @i = 1 
    SET @numrows = (SELECT COUNT(name) FROM ListeClientDB) 
    IF @numrows > 0 
        WHILE (@i <= @numrows) 
        BEGIN 
            SET @Client = (SELECT name FROM ListeClientDB WHERE idx = @i) 
            select @request = 'delete ' + @Client + '..D_BilanCustom'
            exec(@request) 
     
            select @request = ''
     
    		-- Parcours de chaque année comptable du client pour copié l'année concerné du template
            SET @j = 0 
    		SET @numexo = (SELECT nbExo FROM ListeClientDB where name = @Client) 
    		IF @numexo > 0 
    			WHILE (@j < @numexo) 
    			BEGIN 
            		select @request = @request + ' insert into ' + @Client + '..D_BilanCustom ([BilanID],[AcctingYear],...) select [BilanID],' + convert(varchar,@j) + ',... ' +
    				'from Client1813..D_BilanCustom B inner join ' + @Client + '..A_' + right('0'+convert(varchar,@j),2) + '_AParam A on 1 = 1
    				where B.AcctingYear = case when A.AcctingYear <= ''2013'' then 0 else 1 end'
    -- Permet de diviser l'ajout des bilans pour avant 2014 et après 2014 (l'année 2013 = 0 et 2014 = 1 dans le dossier 1813)
     
    				-- increment counter for next Year
    				SET @j = @j + 1 
    			END 
     
            exec(@request) 
            --select(@request) 
            -- increment counter for next Year
            SET @i = @i + 1 
        END

    1. Pour résumer ce code, je récupère les DB client du logiciel dans une table
    2. Je parcours la table pour supprimer certains clients hors scope
    3. Je reparcours la table pour compter le nombre d'années comptables de chaque client (et j'update la table)
    4. Et enfin je parcours chaque clients de la table, je vide la table que je veux updaté et pour chaque année comptable, je copie de mon template


    Voilà en gros ce que fais l'algo.

    On m'a dit un jour que faire un while ou autres en SQL, c'est le mal (SQLpro si tu te reconnais ) et je suis d'accord, mais pour ce cas précis, comment faire ?

  2. #2
    Modérateur

    Homme Profil pro
    Administrateur de base de données
    Inscrit en
    Janvier 2005
    Messages
    5 826
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Administrateur de base de données
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Janvier 2005
    Messages : 5 826
    Par défaut
    Bonjour,

    Il faudrait trouver quelle requête est lente et pourquoi.

    Si votre requête est la seule a s'exécuter, ou qu'elle s'exécute avec un compte de connexion particulier, vous pouvez prendre une trace SQL Profiler côté serveur pour capturer les caractéristiques d'exécution des requêtes et leur plan d'exécution, ou créer une session d'événements étendus.

    A l'inverse, vous pouvez par exemple ajouter SET STATISTICS IO, TIME ON comme première instruction de ce script, et ajouter des PRINT qui vous donneront les temps d'exécution et l'empreinte IO générée par chaque exécution de requête.

    Avec ces éléments, nous pourrons voir précisément où se situe le plus gros de la lenteur.

    @++

  3. #3
    Membre Expert Avatar de Tober
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juillet 2007
    Messages
    824
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : Luxembourg

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Finance

    Informations forums :
    Inscription : Juillet 2007
    Messages : 824
    Par défaut
    Cette requête est lancée seule manuellement depuis SQL Server Management Studio avec un compte Administrateur (le mien).

    J'ai modifié mon script pour inclure "SET STATISTICS IO, TIME ON" et j'ai inclus des traces un peu partout pour me repérer.
    Ce qui prend beaucoup de temps à l'exécution est seulement mes deletes et inserts en fin de script :
    • L'exécution d'une passe de la boucle pour faire les deletes coûte environ 400ms par dossier.
    • L'exécution d'une passe de la boucle pour faire les inserts coûte environ 700ms par dossier.


    Donc si j'exécute mon script pour 10 dossiers, ca prendra au minimum 11s.
    Pour ces chiffres, j'ai exécuté le script plusieurs fois avec les mêmes dossiers, donc le serveur a déjà le plan d'exécution et autres en RAM j'imagine. L'exécution du même script sur 10 autres dossiers est plus long.
    Pour info, j'ai environ 2000 dossiers à tenir à jour...

    Complément d'information :
    Le template à copier se fait sur 3 tables de 49, 9302 et 7671 lignes.

    Donc si optimisation il y a, ca doit se faire sur ces inserts/deletes...

  4. #4
    Modérateur

    Homme Profil pro
    Administrateur de base de données
    Inscrit en
    Janvier 2005
    Messages
    5 826
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Administrateur de base de données
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Janvier 2005
    Messages : 5 826
    Par défaut
    Pour ces chiffres, j'ai exécuté le script plusieurs fois avec les mêmes dossiers, donc le serveur a déjà le plan d'exécution et autres en RAM j'imagine
    C'est effectivement le cas; la compilation d'un plan d'exécution se fait généralement très rapidement.

    L'exécution du même script sur 10 autres dossiers est plus long.
    C'est là que c'est intéressant : s'ils sont plus longs à s'exécuter, comme le texte de votre requête change puisqu'il est affecté par le nom du client, il est possible que le plan de requête sous-jacent soit différent. Le mieux est donc que vous traciez le temps d'exécution des plans pour chaque client et la valeur de la variable @j. En ayant un trace SQL Profiler capturant les requêtes + les plans, nous devrions pouvoir déterminer si les plans sont différents, et s'il y a des opportunités d'optimisation.

    @++

  5. #5
    Membre Expert Avatar de Tober
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juillet 2007
    Messages
    824
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : Luxembourg

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Finance

    Informations forums :
    Inscription : Juillet 2007
    Messages : 824
    Par défaut
    Le temps d'exécution allongé s'explique aussi parce qu'un dossier peut avoir plusieurs années comptables alors que d'autres peuvent en avoir qu'un seul.
    Globalement, toutes ces tables sont plus ou moins équivalentes. Tu soulignes que le plan peut changer par rapport au nom du client. Le nom du client (ou plutot le nom de sa DB) est toujours de la même taille.

    Je veux bien utiliser SQL Server Profiler, mais je ne sais pas bien le configurer ni trop lire un plan d'exécution...
    Quels sont les évènements à ajouter ? Showplan XML, SQL:BatchCompleted... ?
    Est-ce que de simples screenshots suffisent ? (j'ai déjà une 30aine de plans pour un seul dossier, et l'export du fichier fait déjà +1Mo )
    Comment voir ce qui cloche ?
    ... ?

    J'ai vu qu'il existe aussi la possibilité d'utiliser la commande MERGE pour ne pas avoir à tout supprimer et recréer derrière. Est-ce que c'est intéressant ?

  6. #6
    Membre chevronné
    Homme Profil pro
    Consultant en Business Intelligence
    Inscrit en
    Décembre 2007
    Messages
    327
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Consultant en Business Intelligence

    Informations forums :
    Inscription : Décembre 2007
    Messages : 327
    Par défaut
    Bonjour,

    Pour l'utilisation du profiler on va s'aider du tutoriel d'elsuket :


    http://elsuket.developpez.com/tutori...-cote-serveur/


    Au cas ou vous avez un soucis essayer de regarder dans l'activity monitor ce coute le plus a votre base :

    https://msdn.microsoft.com/fr-fr/lib...=SQL.120).aspx
    https://msdn.microsoft.com/en-us/library/ms191511.aspx
    http://www.sqlshack.com/sql-server-activity-monitor/


    Perso (ce ne sont que des supposition, mais j'ai bien peur que votre moteur utilise un mauvais plan du a un manque d'index ou a de mauvaises statistiques) pour cela il faut trouver la ou les requetes consomatrice (en IO et/ou en CPU : comme évoqué plus haut )

    Vu la construction de vos requetes j'utiliserais un Hint (dans notre cas cela s'apparentrai a un pansement) après la contruction de chaque requêtes a savoir :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    'insert into ListeClientDB select '''+@Client+''', 0 from ' + @Client + '.dbo.d_acctgeneral where generalid = ''112'' and wordingfr = ''Primes de fusion'' OPTION (RECOMPILE) '
    Ou

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    'insert into ListeClientDB select '''+@Client+''', 0 from ' + @Client + '.dbo.d_acctgeneral where generalid = ''112'' and wordingfr = ''Primes de fusion'' OPTION (OPTIMIZE FOR UNKNOWN)'
    Ces hints sont a rajouter a la fin de chaque construction de requete pour voir si il y a des améliorations ou nom : changement(s) de plan(s) ...

  7. #7
    Modérateur

    Homme Profil pro
    Administrateur de base de données
    Inscrit en
    Janvier 2005
    Messages
    5 826
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Administrateur de base de données
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Janvier 2005
    Messages : 5 826
    Par défaut
    Le nom du client (ou plutot le nom de sa DB) est toujours de la même taille.
    La taille du nom de la base de données n'a ici aucune influence : en fait, lorsqu'on soumet une requête à SQL Server, il calcule le hash de cette requête, et effectue une recherche de ce hash dans le cash afin de déterminer si un plan pour cette requête existe, et le cas échéant décide d'en calculer un. Donc, le simple fait de modifier / ajouter un caractère, qui peut être de tout type et de toute casse, change la valeur du hash. En revanche, cela n'implique pas nécessairement que le plan sera différent.
    On peut déterminer cela à l'aide des DMVs sys.dm_exec_query_stats et sys.dm_exec_requests, qui exposent toutes deux la valeur de hash du texte de la requête, et la valeur du hash du plan. On peut y voir que pour deux valeurs de query_hash différentes, on peut tout à fait avoir deux valeurs de query_plan hash identiques.

    Quels sont les évènements à ajouter ? Showplan XML, SQL:BatchCompleted... ?
    Effectivement ces deux évènements suffisent. Vous pouvez sélectionner les colonnes TextData, CPU, reads, writes, duration, SPID, StartTime, DatabaseName, RowCount.

    Est-ce que de simples screenshots suffisent ? (j'ai déjà une 30aine de plans pour un seul dossier, et l'export du fichier fait déjà +1Mo )
    Comment voir ce qui cloche ?
    Les impressions d'écran peuvent suffire, mais il est possible que non. L'outil SQL Sentry Plan Explorer permet de visualiser plus simplement que ce que permet SQL Server Management Studio les plans d'exécution, et aussi de les anonymiser (cf. ce billet).
    Pour voir ce qui cloche, il faut corréler la sortie des PRINT que vous avez ajouté avec le plan qui correspond, en ensuite voir ce qui fait que la requête est contre-performante. Si le plan exposé par SQL Profiler ne suffit pas (il n'expose que le plan d'exécution estimé), il faudra ré-exécuter la même requête dans SQL Server Management Studio en capturant le plan d'exécution réel.
    A ce sujet, maintenez-vous les statistiques de colonnes et d'index de toutes vos bases de données ?

    J'ai vu qu'il existe aussi la possibilité d'utiliser la commande MERGE pour ne pas avoir à tout supprimer et recréer derrière. Est-ce que c'est intéressant ?
    La réponse est comme bien souvent : ça dépend
    Vous pouvez utiliser MERGE pour supprimer, modififer ou ajouter des lignes suivant un prédicat de jointure que vous déterminez.
    Cette commande remplace avantageusement un UPDATE + INSERT encadrés par une transaction explicite (BEGIN TRANSACTION ...)

    @++

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. [MySQL] Optimisation de scripts PHP/MySQL
    Par DgG dans le forum PHP & Base de données
    Réponses: 368
    Dernier message: 20/11/2013, 18h59
  2. Optimiser vos scripts
    Par djibril dans le forum Langage
    Réponses: 33
    Dernier message: 11/06/2009, 16h10
  3. [RMAN] script de suppression
    Par alxkid dans le forum Oracle
    Réponses: 3
    Dernier message: 01/09/2004, 07h51
  4. [MySQL] [Script]Optimisation de scripts Php/MySQL (2)
    Par copy dans le forum PHP & Base de données
    Réponses: 8
    Dernier message: 27/08/2004, 08h33
  5. Script de suppression d'archives sous WINDOWS
    Par alxkid dans le forum Administration
    Réponses: 3
    Dernier message: 18/08/2004, 11h11

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