web-dev-qa-db-fra.com

Existe-t-il un moyen de vider la sortie de PL / SQL dans Oracle?

J'ai un script SQL qui est appelé à partir d'un script Shell et prend beaucoup de temps à exécuter. Il contient actuellement dbms_output.put_line déclarations en divers points. La sortie de ces instructions d'impression apparaît dans les fichiers journaux, mais uniquement une fois le script terminé.

Existe-t-il un moyen de garantir que la sortie apparaît dans le fichier journal pendant l'exécution du script?

42
baxter

Pas vraiment. Le fonctionnement de DBMS_OUTPUT est le suivant: votre bloc PL/SQL s'exécute sur le serveur de base de données sans interaction avec le client. Ainsi, lorsque vous appelez PUT_LINE, il s'agit simplement de mettre ce texte dans un tampon en mémoire sur le serveur. Lorsque votre bloc PL/SQL se termine, le contrôle est retourné au client (je suppose SQLPlus dans ce cas); à ce stade, le client extrait le texte du tampon en appelant GET_LINE et l'affiche.

Par conséquent, la seule façon de faire apparaître la sortie dans le fichier journal plus fréquemment est de diviser un grand bloc PL/SQL en plusieurs blocs plus petits, de sorte que le contrôle est retourné plus souvent au client. Cela peut ne pas être pratique selon ce que fait votre code.

D'autres alternatives consistent à utiliser UTL_FILE pour écrire dans un fichier texte, qui peut être vidé à tout moment, ou à utiliser une procédure de transaction autonome pour insérer des instructions de débogage dans une table de base de données et valider après chacune.

51
Dave Costa

Si cela vous est possible, vous devez remplacer les appels à dbms_output.put_line par votre propre fonction.

Voici le code de cette fonction WRITE_LOG - si vous voulez avoir la possibilité de choisir entre 2 solutions de journalisation:

écrire des journaux dans une table dans un transaction autonome

CREATE OR REPLACE PROCEDURE to_dbg_table(p_log varchar2)
  -- table mode: 
  -- requires
  -- CREATE TABLE dbg (u varchar2(200)   --- username
  --                 , d timestamp       --- date
  --                 , l varchar2(4000)  --- log 
  -- );
AS
   pragma autonomous_transaction;
BEGIN
  insert into dbg(u, d, l) values (user, sysdate, p_log);
  commit;
END to_dbg_table;
/

ou écrivez directement sur le serveur DB qui héberge votre base de données

Cela utilise le répertoire OracleTMP_DIR

CREATE OR REPLACE PROCEDURE to_dbg_file(p_fname varchar2, p_log varchar2)
  -- file mode: 
  -- requires
--- CREATE OR REPLACE DIRECTORY TMP_DIR as '/directory/where/Oracle/can/write/on/DB_server/';
AS
  l_file utl_file.file_type;
BEGIN
  l_file := utl_file.fopen('TMP_DIR', p_fname, 'A');
  utl_file.put_line(l_file, p_log);
  utl_file.fflush(l_file);
  utl_file.fclose(l_file);
END to_dbg_file;
/


WRITE_LOG

Ensuite, le WRITE_LOG procédure qui peut basculer entre les 2 utilisations, ou être désactivée pour éviter la perte de performances (g_DEBUG:=FALSE).

CREATE OR REPLACE PROCEDURE write_log(p_log varchar2) AS
  -- g_DEBUG can be set as a package variable defaulted to FALSE
  -- then change it when debugging is required
  g_DEBUG boolean := true;
  -- the log file name can be set with several methods...
  g_logfname varchar2(32767) := 'my_output.log';
  -- choose between 2 logging solutions:
  -- file mode: 
  g_TYPE varchar2(7):= 'file';
  -- table mode: 
  --g_TYPE varchar2(7):= 'table';
  -----------------------------------------------------------------
BEGIN
  if g_DEBUG then
    if g_TYPE='file' then
      to_dbg_file(g_logfname, p_log);
    elsif g_TYPE='table' then
      to_dbg_table(p_log);
    end if;
  end if;  
END write_log;
/

Et voici comment tester ce qui précède:

1) Lancez ceci ( mode fichier) depuis votre SQLPLUS:

BEGIN
  write_log('this is a test');
  for i in 1..100 loop
    DBMS_LOCK.sleep(1);
    write_log('iter=' || i);
  end loop;
  write_log('test complete');
END;
/

2) sur le serveur de base de données, ouvrez un Shell et

 tail -f -n500 /directory/where/Oracle/can/write/on/DB_server/my_output.log
6
J. Chomel

Deux alternatives:

  1. Vous pouvez insérer vos détails de journalisation dans une table de journalisation à l'aide d'une transaction autonome. Vous pouvez interroger cette table de journalisation dans une autre session SQLPLUS/Toad/sql developer etc .... Vous devez utiliser une transaction autonome pour permettre de valider votre journalisation sans interférer avec la gestion des transactions dans votre script sql principal.

  2. Une autre alternative consiste à utiliser une fonction en pipeline qui renvoie vos informations de journalisation. Voir ici pour un exemple: http://berxblog.blogspot.com/2009/01/pipelined-function-vs-dbmsoutput.html Lorsque vous utilisez une fonction en pipeline, vous n'avez pas besoin d'en utiliser une autre Session SQLPLUS/Toad/sql developer etc ...

6
tuinstoel

le tampon de DBMS_OUTPUT est lu lorsque la procédure DBMS_OUTPUT.get_line est appelé. Si votre application client est SQL * Plus, cela signifie qu'elle ne sera vidée qu'une fois la procédure terminée.

Vous pouvez appliquer la méthode décrite dans ce SO pour écrire le DBMS_OUTPUT tampon dans un fichier.

3
Vincent Malgrat

Si vous avez accès au shell système depuis l'environnement PL/SQL, vous pouvez appeler netcat:

 BEGIN RUN_Shell('echo "'||p_msg||'" | nc '||p_Host||' '||p_port||' -w 5'); END;

p_msg - est un message de journal v_Host est un hôte exécutant python qui lit les données du socket sur le port v_port.

J'ai utilisé cette conception lorsque j'ai écrit aplogr pour la surveillance en temps réel des journaux Shell et pl/sql.

1
olekb