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

PL/SQL Oracle Discussion :

Amélioration d'un process d'écriture


Sujet :

PL/SQL Oracle

  1. #1
    Modérateur
    Avatar de Waldar
    Homme Profil pro
    Sr. Specialist Solutions Architect @Databricks
    Inscrit en
    Septembre 2008
    Messages
    8 454
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Sr. Specialist Solutions Architect @Databricks
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2008
    Messages : 8 454
    Par défaut Amélioration d'un process d'écriture
    Bonjour à tous,


    Dans le cadre d'un développement je dois extraire depuis une base de données des fichiers XML, afin de les envoyer à des tiers.

    Pour générer le XML, j'ai codé ce qu'il faut dans une vue en passant pas des objets créé pour le besoin : pas de soucis de ce côté là, ça répond plutôt bien.

    L'envoi des fichiers est piloté par une table tierce qui contient les méta-données.

    Là où je ne suis pas satisfait de mon process, c'est sur l'extraction de ces XML vers les fichiers.

    Certaines limitations me sont imposées :
    • dans ma table d'envoi figure un code et je dois déclarer un fichier par code.
    • un fichier ne doit pas dépasser 10.000 objets.


    Mon environnement est Oracle Database Enterprise Edition 11gR1 11.1.0.7, 64 bits qui opère sur Windows Server 2003.

    J'ai créé un jeu de test relativement simple pour illustrer tout ceci :
    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
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    create directory TST as 'c:\TEMP\TST';
     
    create table donnees
    (
        col1      number(1)         not null,
        col2      varchar2(10 char) not null,
        constraint pk_donnees
          primary key (col1)
          using index
    );
     
    insert into donnees (col1, col2) values (1, 'ABC');
    insert into donnees (col1, col2) values (2, 'DEF');
    insert into donnees (col1, col2) values (3, 'GHI');
    insert into donnees (col1, col2) values (4, 'JKL');
    insert into donnees (col1, col2) values (5, 'MNO');
    insert into donnees (col1, col2) values (6, 'PQR');
    insert into donnees (col1, col2) values (7, 'STU');
    insert into donnees (col1, col2) values (8, 'VWX');
    insert into donnees (col1, col2) values (9, 'YZ' );
     
    commit;
     
    create view v_donnees_xml
    as
    select col1, XMLElement("SuperBalise", col2) as col_XML
      from donnees;
     
    create table envoi
    (
        col1      number(1)         not null,
        code      varchar2(1 char)  not null,
        chk       varchar2(1 char)      null,
        constraint pk_envoi
          primary key (col1)
          using index
    );
     
    insert into envoi (col1, code, chk) values (1, 'A', 'X');
    insert into envoi (col1, code, chk) values (2, 'A', 'X');
    insert into envoi (col1, code, chk) values (3, 'A', 'X');
    insert into envoi (col1, code, chk) values (4, 'A', 'X');
    insert into envoi (col1, code, chk) values (5, 'A', 'X');
    insert into envoi (col1, code, chk) values (6, 'B', 'X');
    insert into envoi (col1, code, chk) values (7, 'B', 'X');
    insert into envoi (col1, code, chk) values (8, 'B', 'X');
    insert into envoi (col1, code, chk) values (9, 'C', 'X');
     
    commit;
    D'après ce que j'ai lu ici (probablement un lien posté par mnitu, comme souvent ), le plus rapide sans parallélisation pour exporter des données dans un fichier plat sont de passer par un CLOB et de ne l'écrire qu'une fois.

    Dans le code PL/SQL qui va suivre, ce qui me déplaît c'est que j'ai créé un premier curseur pour parcourir les éléments à envoyer, et à partir des données de celui-ci je vais chercher le bout de XML qui me convient.
    Avec ma volumétrie, effectuer la jointure à ce niveau en pur SQL est malheureusement trop coûteux, j'annule le teste après plus de trente minutes sans rien voir arriver.

    Dans ce code j'ai simulé la limitation à trois objets par fichier :
    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
    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
    declare
        v_file                  CLOB;
        v_clob                  CLOB;
        v_date_extract          VARCHAR2(30);
        v_nb_rows               NUMBER(10) DEFAULT 0;
        v_file_name             VARCHAR2(50);
        v_file_dir     CONSTANT VARCHAR2( 3) DEFAULT 'TST';
        v_header                VARCHAR2(100);
        v_footer                VARCHAR2(100);
     
    begin
     
        for c_query in
        (
        select col1, code,
               lag(code, 1, ' ') over(order by code asc) as code_prec
          from envoi
         where chk = 'X'
        )
        loop
     
          if (c_query.code <> c_query.code_prec or v_nb_rows = 3)
          then -- Nouveau fichier 
     
            if c_query.code_prec <> ' '
            then -- Écriture du fichier précédent
              v_footer := lpad(' ', 4, ' ') || '<NbItem>' || to_char(v_nb_rows) || '</NbItem>' || chr(13) || chr(10) || '</MonFichier>';
              -- Rajout du footer
              DBMS_LOB.WRITEAPPEND(v_file, length(v_footer), v_footer);
              DBMS_XSLPROCESSOR.CLOB2FILE(v_file, v_file_dir, v_file_name);
              DBMS_LOB.FREETEMPORARY(v_file);
            end if;
     
            -- Initialisation du CLOB 
            DBMS_LOB.CREATETEMPORARY(v_file, TRUE);
            v_nb_rows      := 0;
            v_date_extract := to_char(systimestamp, 'yyyymmdd_hh24missff');
            v_file_name    := 'Fichier_' || c_query.code || '_' || v_date_extract || '.xml';
     
            v_header       := '<MonFichier>' || chr(13) || chr(10) ||
                              lpad(' ', 4, ' ') || '<MonCode>'   || c_query.code   || '</MonCode>'   || chr(13) || chr(10) ||
                              lpad(' ', 4, ' ') || '<DateEnvoi>' || v_date_extract || '</DateEnvoi>' || chr(13) || chr(10);
     
            -- Rajout du header
            DBMS_LOB.WRITEAPPEND(v_file, length(v_header), v_header);
     
          end if;
     
          -- Sélection du XML dans la vue 
          select lpad(' ', 8, ' ') || x.col_xml.getClobVal() || chr(13) || chr(10) into v_clob
            from v_donnees_xml x
           where x.col1 = c_query.col1;
     
          -- Concaténation du XML dans le CLOB
          DBMS_LOB.APPEND(v_file, v_clob);
     
          v_nb_rows := v_nb_rows + 1;
     
        end loop;
     
        -- Écriture du dernier fichier
        v_footer := lpad(' ', 4, ' ') || '<NbItem>' || to_char(v_nb_rows) || '</NbItem>' || chr(13) || chr(10) || '</MonFichier>';
        -- Rajout du footer
        DBMS_LOB.WRITEAPPEND(v_file, length(v_footer), v_footer);
        DBMS_XSLPROCESSOR.CLOB2FILE(v_file, v_file_dir, v_file_name);
        DBMS_LOB.FREETEMPORARY(v_file);
     
    end;
    /
    Évidemment sur ce genre de jeu de test, tout est instantané.

    Voici le contenu d'un fichier, qui est bien celui escompté :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    <MonFichier>
        <MonCode>A</MonCode>
        <DateEnvoi>20110329_151939417000000</DateEnvoi>
            <SuperBalise>JKL</SuperBalise>
            <SuperBalise>MNO</SuperBalise>
        <NbItem>2</NbItem>
    </MonFichier>
    Sur mon environnement de développement, j'écris environ 1Go de données en quinze minutes.
    Ce n'est pas tellement l'écriture qui est lente, c'est vraiment d'aller chercher les bouts de XML un par un.

    C'est sur ce point qu'à mon avis le code peut être améliorer, je suis quasiment sûr que ce temps de traitement est divisible par deux ou trois.
    J'ai imaginé stocker tous mes identifiants dans une collection et la passer d'un coup à la vue, avec du XMLAgg afin de ne faire plus qu'un seul appel à ce niveau, mais je n'ai pas réussi à l'implémenter.

    Ayant relativement peu de pratique en pur PL/SQL, toutes expériences et idées sont les bienvenues !

    Merci d'être parvenu au terme de ce long sujet.

    PS : Pour nettoyer votre environnement :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    drop view v_donnees_xml;
     
    drop table envoi;
     
    drop table donnees;
     
    drop directory TST;

  2. #2
    Expert confirmé Avatar de mnitu
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Octobre 2007
    Messages
    5 611
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Marne (Champagne Ardenne)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Octobre 2007
    Messages : 5 611
    Par défaut
    Salut Waldar,
    Oui travailler ligne par ligne n’est pas bon pour les performances.
    Si j’ai bien compris ton problème il te faut grouper les donnés des le départ et parcourir les données dans un seul passage.
    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
    26
    27
    28
     
    Connected to Oracle Database 10g Enterprise Edition Release 10.2.0.4.0 
    Connected as mni
     
    SQL> 
    SQL> Select code, XmlAgg(col_XML)
      2    From (
      3          Select t.*, trunc(t.rn/4) grp
      4          From (
      5            SELECT e.code, x.col_xml, row_number() over (partition by code order by rownum)rn
      6              FROM v_donnees_xml x
      7                   Join
      8                   envoi e
      9                On e.col1 = x.col1
     10            Where  e.chk = 'X'
     11          ) t
     12  )
     13  Group By code, grp
     14  /
     
    CODE XMLAGG(COL_XML)
    ---- --------------------------------------------------------------------------------
    A    <SuperBalise>ABC</SuperBalise><SuperBalise>DEF</SuperBalise><SuperBalise>GHI</Su
    A    <SuperBalise>JKL</SuperBalise><SuperBalise>MNO</SuperBalise>
    B    <SuperBalise>PQR</SuperBalise><SuperBalise>STU</SuperBalise><SuperBalise>VWX</Su
    C    <SuperBalise>YZ</SuperBalise>
     
    SQL>
    PS. Ce n’était pas moi. J’ai simplement participé à la discussion pour signaler que l’utilisation du Clob n’est pas meilleure que l’utilisation correcte de l’UTL_FILE

  3. #3
    Modérateur
    Avatar de Waldar
    Homme Profil pro
    Sr. Specialist Solutions Architect @Databricks
    Inscrit en
    Septembre 2008
    Messages
    8 454
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Sr. Specialist Solutions Architect @Databricks
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2008
    Messages : 8 454
    Par défaut
    Tout simplement le trunc du row_number(), je n'y ai pas pensé.

    Merci mnitu, je reviens avec le différentiel de temps de traitement.

  4. #4
    Modérateur
    Avatar de Waldar
    Homme Profil pro
    Sr. Specialist Solutions Architect @Databricks
    Inscrit en
    Septembre 2008
    Messages
    8 454
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Sr. Specialist Solutions Architect @Databricks
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2008
    Messages : 8 454
    Par défaut
    Aïe, même si l'idée est excellente, elle a mis le serveur sur les rotules.
    Mon temps de référence est de treize minutes et après une demie-heure je n'ai toujours rien qui vient.

    Les CPU sont à 100%, je pense que le XMLAgg sur toutes les lignes est trop violent.

    Je vais regarder si je peux implémenter une solution intermédiaire, peut-être essayer de passer les id 100 par 100, mais là je retombe dans ma connaissance trop faible des collections.

    Ou alors si une autre idée surgit !

  5. #5
    Expert confirmé Avatar de mnitu
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Octobre 2007
    Messages
    5 611
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Marne (Champagne Ardenne)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Octobre 2007
    Messages : 5 611
    Par défaut
    C'est le fait du chercher ligne à ligne qui prends le plus du temps ou la concaténation des lobs ? C'est dans ce cas que le profil du traitement devrait indiquer dans où le temps passe et par conséquence comment il faut intervenir.
    Quelle est la volumétrie de la table ?

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

    Informations forums :
    Inscription : Août 2008
    Messages : 2 952
    Par défaut
    Salut Waldar,

    Alors effectivement nous ne savons pas encore lequel, du select dans la boucle ou de la concaténation des lobs, est le plus lent, mais au cas où le select dans la boucle soit en cause je propose une autre approche (mais franchement je ne sais pas ce que ça vaut, et vu que vous êtes tous les deux plus compétent que moi... j'ai de gros doutes...)

    Mon idée est, plutôt que de générer du XML en SQL et découper les fichiers en PL/SQL, pourquoi ne pas faire l'inverse... découper en SQL et générer le XML dans le code PL/SQL (à la manière du header et du footer déjà codés), au moins il n'y a plus de select dans une boucle.

    J'ai utilisé une variable globale pour "simuler" le paramètre v_nb_rows tel qu'il était utilisé dans le découpage, mais une modification assez simple te l'épargnera si tu préfères.
    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
    26
    create or replace package param as
      g_nb_rows NUMBER(10) DEFAULT 3;
      function getNbRows return number;
    end;
    /
    create or replace package body param as
      function getNbRows return number is
        begin
          return g_nb_rows;
        end;
    end;
    /
    create or replace view v_donnees_per_file as
    with rows_per_files as (
      select param.getNbRows as nb_rows from dual
    )
      select t.code,t.col2,
             mod(t.nb_rows - 1,rpf.nb_rows) as new_file
        from (select e.code,d.col2,
                     count(*) over (partition by e.code order by d.col2) as nb_rows
                from envoi e
                join donnees d on e.col1 = d.col1
               where e.chk = 'X'
             ) t
       cross join rows_per_files rpf
    /
    et la procédure (très proche de la tienne):
    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
    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
    declare
        v_file                  CLOB;
        v_clob                  CLOB;
        v_date_extract          VARCHAR2(30);
        v_nb_rows               NUMBER(10) DEFAULT 0;
        v_file_name             VARCHAR2(50);
        v_file_dir     CONSTANT VARCHAR2( 3) DEFAULT 'TST';
        v_header                VARCHAR2(100);
        v_footer                VARCHAR2(100);
        v_first_row             VARCHAR2(1) DEFAULT 'T';
     
    begin
     
        FOR c_query IN
        (
        select code,col2,new_file from v_donnees_per_file
        )
        loop
     
          IF (c_query.new_file = 0)
          then -- Nouveau fichier 
     
            IF v_first_row <> 'T'
            then -- Écriture du fichier précédent
              v_footer := lpad(' ', 4, ' ') || '<NbItem>' || to_char(v_nb_rows) || '</NbItem>' || chr(13) || chr(10) || '</MonFichier>';
              -- Rajout du footer
              DBMS_LOB.WRITEAPPEND(v_file, length(v_footer), v_footer);
              DBMS_XSLPROCESSOR.CLOB2FILE(v_file, v_file_dir, v_file_name);
              DBMS_LOB.FREETEMPORARY(v_file);
            end IF;
            v_first_row := 'F';    
            -- Initialisation du CLOB 
            DBMS_LOB.CREATETEMPORARY(v_file, TRUE);
            v_nb_rows      := 0;
            v_date_extract := to_char(systimestamp, 'yyyymmdd_hh24missff');
            v_file_name    := 'Fichier_' || c_query.code || '_' || v_date_extract || '.xml';
     
            v_header       := '<MonFichier>' || chr(13) || chr(10) ||
                              lpad(' ', 4, ' ') || '<MonCode>'   || c_query.code   || '</MonCode>'   || chr(13) || chr(10) ||
                              lpad(' ', 4, ' ') || '<DateEnvoi>' || v_date_extract || '</DateEnvoi>' || chr(13) || chr(10);
     
            -- Rajout du header
            DBMS_LOB.WRITEAPPEND(v_file, length(v_header), v_header);
     
          end IF;
     
          -- Sélection du XML dans la vue 
          -- Plus utile maintenant
          -- SELECT lpad(' ', 8, ' ') || x.col_xml.getClobVal() || chr(13) || chr(10) INTO v_clob
          --  FROM v_donnees_xml x
          -- WHERE x.col1 = c_query.col1;
     
          v_clob := lpad(' ', 8, ' ') || '<SuperBalise>'||c_query.col2||'</SuperBalise>' || chr(13) || chr(10);
     
          -- Concaténation du XML dans le CLOB
          DBMS_LOB.APPEND(v_file, v_clob);
     
          v_nb_rows := v_nb_rows + 1;
     
        end loop;
     
        -- Écriture du dernier fichier
        v_footer := lpad(' ', 4, ' ') || '<NbItem>' || to_char(v_nb_rows) || '</NbItem>' || chr(13) || chr(10) || '</MonFichier>';
        -- Rajout du footer
        DBMS_LOB.WRITEAPPEND(v_file, length(v_footer), v_footer);
        DBMS_XSLPROCESSOR.CLOB2FILE(v_file, v_file_dir, v_file_name);
        DBMS_LOB.FREETEMPORARY(v_file);
     
    end;
    /
    Mais je ne sais pas dans quelle mesure il sera simple d'adapter mon approche à tes données, notamment quand tu dis :
    j'ai codé ce qu'il faut dans une vue en passant par des objets créé pour le besoin

  7. #7
    Modérateur
    Avatar de Waldar
    Homme Profil pro
    Sr. Specialist Solutions Architect @Databricks
    Inscrit en
    Septembre 2008
    Messages
    8 454
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Sr. Specialist Solutions Architect @Databricks
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2008
    Messages : 8 454
    Par défaut
    mnitu, je pensais perdre du temps sur l'appel un par un dans la vue XML à partir du curseur, mais justement ton test m'amène à penser qu'il faut jongler entre les deux coûts : l'appel récurrent ou la construction du XML directement par le moteur SQL.

    J'ai lancé des tests unitaires en utilisant ton code, le XMLAgg sur le premier bloc dure environ cinq minutes alors que la sélection unitaire dans un CLOB global ne prend qu'une ou deux minutes.

    J'ai exécuté une trace sur mon traitement de référence, j'attends que le DBA sorte de réunion pour que je puisse l'analyser, puis j'en relancerai une sur le test unitaire du XMLAgg.

    Au niveau de la volumétrie, c'est plutôt faible au départ.
    Dans le modèle relationnel dédié, tables et index occupent 250 Mo.

    Le XML étant un langage verbeux, à partir de ce volume je génère un peu plus d'un Go de données.

    skuatamad, dans le test c'est faisable, malheureusement en vrai ma vue pour générer le XML fait plus de 200 lignes et possède sa petite complexité, ça va être difficile à réaliser.

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

    Informations forums :
    Inscription : Août 2008
    Messages : 2 952
    Par défaut
    J'ai probablement été un peu violent en retirant tout le XML de la vue, mais si la requête "prédécoupe un peu" en fichier, la concaténation oppéré par le XMLAgg se fera facilement dans le PL/SQL et il n'y aura pas de requête dans la boucle.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    SELECT t.code,t.col_xml,
           mod(t.nb_rows - 1,3) AS new_file
      FROM (SELECT e.code, x.col_xml, 
                   count(*) over (partition BY e.code ORDER BY rownum) AS nb_rows
              FROM v_donnees_xml x
              JOIN envoi e ON e.col1 = x.col1
             WHERE e.chk = 'X'
            ) t

  9. #9
    McM
    McM est déconnecté
    Expert confirmé

    Homme Profil pro
    Développeur Oracle
    Inscrit en
    Juillet 2003
    Messages
    4 580
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur Oracle

    Informations forums :
    Inscription : Juillet 2003
    Messages : 4 580
    Billets dans le blog
    4
    Par défaut
    A mon avis, ce qui est coûteux, c'est l'affectation sur CLOB et la concaténation. J'avais ce problème sur de la génération de SOAP XML.

    Tant que tu peux utiliser du VARCHAR2 pour concaténer des chaines, fais le.
    Le Append sur ton CLOB global quand tu en as besoin.
    Sinon, tu as essayé uniquement en varchar2 pour remplir ton fichier ?

    Dernière ou première action : Mettre des traces (dbms_utility.get_time) entre chaque partie pour calculer le temps total de chaque instruction. C'est comme ça que j'avais vu que les append de CLOB étaient super longs.

  10. #10
    Expert confirmé Avatar de mnitu
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Octobre 2007
    Messages
    5 611
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Marne (Champagne Ardenne)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Octobre 2007
    Messages : 5 611
    Par défaut
    Salut Waldar,
    Je pense que t’as peut être, un peut trop simplifié ton problème. Dans ce qui suit la table big (N fois select * from all_objects) contient presque 800 000 enregistrements. Une colonne id a été ajoutée et valorisée à partir du rownum pour devenir la clé primaire. La table big_pilote simule la table envoie (id, owner, object_type), clé primaire id. La vue donnes XML est
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    CREATE OR REPLACE VIEW DONNEES_XML AS
    SELECT id, XMLElement("SuperBalise", object_name) AS col_XML
      FROM big
    La requête suivante ramène 17 enregistrements en 6 secondes
    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
    26
    27
    28
    29
    30
    31
    32
    33
    34
     
    Select object_type, XmlAgg(col_XML).extract('/') xml, count(*)
      From (
        Select t.*, trunc((rn - 1)/10000) grp
          From (
                Select b.owner, b.object_type, a.id, a.col_XML,
                       row_number() over(partition by b.owner, b.object_type order by b.id) rn
                  From donnees_xml a
                       Join
                       big_pilote b
                    On a.id = b.id
                 Where b.owner = 'TOTO'
        ) t   
    )
    Group By object_type, grp
    /
       	OBJECT_TYPE	XML	COUNT(*)
    1	TYPE		<CLOB>	320
    2	VIEW		<CLOB>	8448
    3	INDEX		<CLOB>	10000
    4	INDEX		<CLOB>	5168
    5	TABLE		<CLOB>	10000
    6	TABLE		<CLOB>	4560
    7	PACKAGE		<CLOB>	1344
    8	SYNONYM		<CLOB>	64
    9	TRIGGER		<CLOB>	10000
    10	TRIGGER		<CLOB>	3344
    11	FUNCTION 	<CLOB>	1024
    12	SEQUENCE 	<CLOB>	1120
    13	PROCEDURE	<CLOB>	10000
    14	PROCEDURE	<CLOB>	10000
    15	PROCEDURE	<CLOB>	10000
    16	PROCEDURE	<CLOB>	4560
    17	PACKAGE BODY	<CLOB>	1344
    Chaque clob contient count(*) enregistrements de type
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    <SuperBalise>JKL</SuperBalise>
    <SuperBalise>MNO</SuperBalise>

  11. #11
    Modérateur
    Avatar de Waldar
    Homme Profil pro
    Sr. Specialist Solutions Architect @Databricks
    Inscrit en
    Septembre 2008
    Messages
    8 454
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Sr. Specialist Solutions Architect @Databricks
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2008
    Messages : 8 454
    Par défaut
    Oui c'est fort possible, là où j'ai simplifié le problème c'est dans la vue qui retourne le XML, en réalité je récupère par objet entre 5000 et 150.000 caractères.

    Je n'ai pas encore essayé de les découper en varchar2, c'est la prochaine étape.

    J'ai pu récupérer mes traces en attendant.

    Traitement de référence
    Requête du curseur :
    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
    call     count       cpu    elapsed       disk      query    current        rows
    ------- ------  -------- ---------- ---------- ---------- ----------  ----------
    Parse        1      0.00       0.00          0          0          0           0
    Execute      1      0.00       0.00          0          0          0           0
    Fetch    88175      1.75       2.07        964       1008          0       88174
    ------- ------  -------- ---------- ---------- ---------- ----------  ----------
    total    88177      1.75       2.07        964       1008          0       88174
     
    Misses in library cache during parse: 1
    Optimizer mode: ALL_ROWS
     
    Elapsed times include waiting on following events:
      Event waited on                             Times   Max. Wait  Total Waited
      ----------------------------------------   Waited  ----------  ------------
      db file scattered read                         37        0.04          0.47
      db file sequential read                         8        0.00          0.01
    ********************************************************************************
    Requêtes qui vont chercher le XML un par un :
    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
    call     count       cpu    elapsed       disk      query    current        rows
    ------- ------  -------- ---------- ---------- ---------- ----------  ----------
    Parse        1      0.00       0.00          0          0          0           0
    Execute  88174      8.35       8.91         76        759          0           0
    Fetch    88174    429.15     473.41      14418    1243061     705119       88174
    ------- ------  -------- ---------- ---------- ---------- ----------  ----------
    total   176349    437.51     482.33      14494    1243820     705119       88174
     
    Misses in library cache during parse: 1
    Misses in library cache during execute: 1
     
    Elapsed times include waiting on following events:
      Event waited on                             Times   Max. Wait  Total Waited
      ----------------------------------------   Waited  ----------  ------------
      db file sequential read                     15607        0.44         38.74
    ********************************************************************************
    Sur le reste du traitement :
    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
    call     count       cpu    elapsed       disk      query    current        rows
    ------- ------  -------- ---------- ---------- ---------- ----------  ----------
    Parse        1      0.00       0.00          0          0          0           0
    Execute      1    132.57     247.87       6434    1527790    9074615           1
    Fetch        0      0.00       0.00          0          0          0           0
    ------- ------  -------- ---------- ---------- ---------- ----------  ----------
    total        2    132.57     247.87       6434    1527790    9074615           1
     
    Misses in library cache during parse: 1
    Optimizer mode: ALL_ROWS
     
    Elapsed times include waiting on following events:
      Event waited on                             Times   Max. Wait  Total Waited
      ----------------------------------------   Waited  ----------  ------------
      db file sequential read                      4383        0.32          4.45
      db file scattered read                       1022        0.18          1.49
      latch: object queue header operation            1        0.02          0.02
      SQL*Net message to client                       1        0.00          0.00
      SQL*Net message from client                     1        0.01          0.01
    ********************************************************************************
    Traitement allégé cf. mnitu
    J'ai conservé l'idée de construire le fichier directement dans le curseur, et je fais un XMLAgg en utilisant ma plage d'identifiants récupérés directement à partir du curseur.
    Cette fois-ci je parviens à un résultat, mais environ deux fois moins rapide que le traitement de référence.
    Requête du curseur :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    call     count       cpu    elapsed       disk      query    current        rows
    ------- ------  -------- ---------- ---------- ---------- ----------  ----------
    Parse        1      0.00       0.00          0          0          0           0
    Execute      1      0.00       0.00          0          0          0           0
    Fetch       13      0.00       0.31          0       1008          0          12
    ------- ------  -------- ---------- ---------- ---------- ----------  ----------
    total       15      0.00       0.31          0       1008          0          12
     
    Misses in library cache during parse: 1
    Optimizer mode: ALL_ROWS
    Requêtes qui vont chercher les XMLAgg :
    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
    call     count       cpu    elapsed       disk      query    current        rows
    ------- ------  -------- ---------- ---------- ---------- ----------  ----------
    Parse        1      0.00       0.00          0          0          0           0
    Execute     12      0.06       0.06          0        783          0           0
    Fetch       12   1086.32    1347.93     147634    9884451   72114827          12
    ------- ------  -------- ---------- ---------- ---------- ----------  ----------
    total       25   1086.39    1348.00     147634    9885234   72114827          12
     
    Misses in library cache during parse: 1
    Misses in library cache during execute: 1
    Optimizer mode: ALL_ROWS
     
    Elapsed times include waiting on following events:
      Event waited on                             Times   Max. Wait  Total Waited
      ----------------------------------------   Waited  ----------  ------------
      write complete waits                            4        0.99          3.96
      db file sequential read                     73312        1.32        154.43
      latch: cache buffers chains                     1        0.00          0.00
      db file scattered read                      37029        2.26         63.08
      latch: object queue header operation            1        0.00          0.00
      latch: cache buffers lru chain                  1        0.00          0.00
    ********************************************************************************
    Sur le reste du traitement :
    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
    call     count       cpu    elapsed       disk      query    current        rows
    ------- ------  -------- ---------- ---------- ---------- ----------  ----------
    Parse        1      0.00       0.00          0          0          0           0
    Execute      1      0.00     252.44      40451    1760811   11465481           1
    Fetch        0      0.00       0.00          0          0          0           0
    ------- ------  -------- ---------- ---------- ---------- ----------  ----------
    total        2      0.00     252.44      40451    1760811   11465481           1
     
    Misses in library cache during parse: 1
    Optimizer mode: ALL_ROWS
     
    Elapsed times include waiting on following events:
      Event waited on                             Times   Max. Wait  Total Waited
      ----------------------------------------   Waited  ----------  ------------
      db file sequential read                     40450        0.24         28.33
      SQL*Net message to client                       1        0.00          0.00
      SQL*Net message from client                     1        0.01          0.01
    ********************************************************************************
    J'ai suivi le conseil de McM pour mesurer les temps de DBMS_LOB.APPEND, ça m'a l'air plutôt correct.
    Les temps pour un fichier (les autres sont dans les mêmes ordre de grandeur) en centièmes de secondes :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    header           0
    XMLAgg       13322
    append clob    658
    footer          37
    write disk    1521

  12. #12
    Expert confirmé Avatar de mnitu
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Octobre 2007
    Messages
    5 611
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Marne (Champagne Ardenne)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Octobre 2007
    Messages : 5 611
    Par défaut
    Requêtes qui vont chercher le XML un par un :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    call     disk      query    current        rows
    ------- ------  -------- ---------- ---------- ---------- ----------  ----------
    Fetch    14418    1243061     705119       88174
    ------- ------  -------- ---------- ---------- ---------- ----------  ----------
    total   14494    1243820     705119       88174
    Requêtes qui vont chercher les XMLAgg :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    call     disk      query    current        rows
    ------- ------  -------- ---------- ---------- ---------- ----------  ----------
    Fetch       147634    9884451   72114827          12
    ------- ------  -------- ---------- ---------- ---------- ----------  ----------
    total       147634    9885234   72114827          12
    Que est-ce qu'il explique cette différence ?
    Que voit-on dans les plans d'exécution ?

  13. #13
    Modérateur
    Avatar de Waldar
    Homme Profil pro
    Sr. Specialist Solutions Architect @Databricks
    Inscrit en
    Septembre 2008
    Messages
    8 454
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Sr. Specialist Solutions Architect @Databricks
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2008
    Messages : 8 454
    Par défaut
    L'explain plan me paraît correct.

    Sur le traitement un à un, il fait ce qui est attendu, index unique scan + rowid access.
    Sur le traitement ensembliste, il fait un index full scan + rowid access + hash join...
    Par contre sur une de mes tables de mon modèle (que j'appelle aussi en XMLAgg dans ma vue), je me retrouve avec cette interprétation dans l'EXPLAIN PLAN de la trace :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
       9817  SORT AGGREGATE (cr=18728 pr=0 pw=0 time=0 us)
     142105   INDEX RANGE SCAN PK_TABLE2 (cr=17408 pr=0 pw=0 time=62281 us cost=3 size=1674 card=18)
    Je n'ai pas réussi à interpréter le time=62281, mais j'imagine que ce n'est pas bon (j'ai 0 partout ailleurs).

    J'ai pu améliorer le traitement initial (edit : en fait temps similaire, mais code relativement plus simple) avec les idées récupérées ici + l'utilisation d'une collection.
    J'ai un peu moins de code pour la gestion des fichiers en créant dès le départ les bonnes plages :
    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
    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
    create type nt_col1 as table of number(1);
     
    declare
        v_file                  CLOB;
        v_clob                  CLOB;
        v_date_extract          VARCHAR2( 30);
        v_file_name             VARCHAR2( 50);
        v_file_dir     CONSTANT VARCHAR2(  3) DEFAULT 'TST';
        v_header                VARCHAR2(100);
        v_footer                VARCHAR2(100);
     
    begin
     
        FOR c_query IN
        (
        With sr as
        (
        SELECT col1, code,
               trunc((row_number() over (partition BY code ORDER BY col1 asc)-1)/3) as grp
          FROM envoi
         WHERE chk = 'X'
        )
          SELECT code, grp,
                 count(*) as nb_rows,
                 cast(collect(col1) as nt_col1) as tt
            FROM sr
        GROUP BY code, grp
        ORDER BY code, grp
        )
        loop
     
          -- Initialisation du CLOB 
          DBMS_LOB.CREATETEMPORARY(v_file, TRUE);
          v_date_extract := to_char(systimestamp, 'yyyymmdd_hh24missff');
          v_file_name    := 'Fichier_' || c_query.code || '_' || v_date_extract || '.xml';
     
          v_header       := '<MonFichier>' || chr(13) || chr(10) ||
                            lpad(' ', 4, ' ') || '<MonCode>'   || c_query.code   || '</MonCode>'   || chr(13) || chr(10) ||
                            lpad(' ', 4, ' ') || '<DateEnvoi>' || v_date_extract || '</DateEnvoi>' || chr(13) || chr(10);
     
          -- Rajout du header
          DBMS_LOB.WRITEAPPEND(v_file, length(v_header), v_header);
     
          for i in c_query.tt.first..c_query.tt.last
          loop
     
            -- Sélection du XML dans la vue 
            SELECT lpad(' ', 8, ' ') || x.col_xml.getClobVal() || chr(13) || chr(10) INTO v_clob
              FROM v_donnees_xml x
             WHERE x.col1 = c_query.tt(i);
     
            -- Concaténation du XML dans le CLOB
            DBMS_LOB.APPEND(v_file, v_clob);
     
          end loop;
     
          -- Écriture du fichier
          v_footer := lpad(' ', 4, ' ') || '<NbItem>' || to_char(c_query.nb_rows) || '</NbItem>' || chr(13) || chr(10) || '</MonFichier>';
     
          -- Rajout du footer
          DBMS_LOB.WRITEAPPEND(v_file, length(v_footer), v_footer);
          DBMS_XSLPROCESSOR.CLOB2FILE(v_file, v_file_dir, v_file_name);
          DBMS_LOB.FREETEMPORARY(v_file);
     
        end loop;
     
    end;
    /
    Malheureusement mon crédit-temps sur ce projet touchant à sa fin, je vais me contenter de ces améliorations.

    Merci à tous, toutes vos propositions m'ont bien aidées.

  14. #14
    Expert confirmé Avatar de mnitu
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Octobre 2007
    Messages
    5 611
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Marne (Champagne Ardenne)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Octobre 2007
    Messages : 5 611
    Par défaut
    Je continu à penser que parcourir les données une seul fois devrait donner un meilleurs temps de réponse. Mais si le temps de réponse est acceptable c’est OK.
    Par contre la solution qui détermine d’abord les plages des identifiants à extraire permet une optimisation plus intéressante encore qui via la parallélisassions des traitements d’écriture des fichiers.

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

Discussions similaires

  1. [Java I/O] Ouverture et écriture par plusieurs process Java
    Par arkham55 dans le forum Débuter avec Java
    Réponses: 5
    Dernier message: 11/05/2013, 01h08
  2. Identifier les fichiers en cours d'écriture par un process ext
    Par sinmaniphel dans le forum Entrée/Sortie
    Réponses: 2
    Dernier message: 24/01/2007, 09h22
  3. Réponses: 4
    Dernier message: 01/07/2003, 15h47
  4. [DOM] Ajout d'une instrution de processing
    Par corwin_d_ambre dans le forum Format d'échange (XML, JSON...)
    Réponses: 9
    Dernier message: 06/05/2003, 11h51
  5. [Kylix] Simplifications de l'écriture Kylix/Pascal"
    Par Mr Vincent KLEIN dans le forum EDI
    Réponses: 1
    Dernier message: 11/03/2003, 11h07

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