viernes, 28 de noviembre de 2008

¿Cómo deshacernos de un cursor en el Shared Pool?

Seguramente en alguna oportunidad has necesitado que un procedimiento almacenado o una secuencia permanezca en el Shared Pool y has terminado usando el package dbms_shared_pool, mientras que si lo que deseabas era deshacerte de ellos, la única alternativa era recurrir a alter system flush shared_pool, sentencia que limpia todo el Shared Pool con lo que ello implica. La buena noticia es que con la aparición de 11g se introdujo un método adicional al package dbms_shared_pool, mismo que permite la eliminación selectiva de objetos residentes en el Shared Pool, pero ¿sabías que este método también está disponible en 10gR2?, pues así es, pero su uso requiere de un paso adicional, que explicaré a continuación con algunos ejemplos.

El método del cual estamos hablando es el procedimiento purge, que permite eliminar del Shared Pool cursores, secuencias, packages, triggers, etc. Como les indicara, apareció con 11g pero viene incluido también con 10.2.0.4, y para las versiones 10.2.0.2 y 10.2.0.3 lo puedes obtener como un patch, el 5614566, pero debes tener en cuenta que en esta forma no está disponible para todas las plataformas.

La mejor forma de aprender a usarlo es mediante algunos ejemplos, para ello empezaremos viendo la forma de eliminar un cursor específico, en este caso se trata del cursor asociado al siguiente query:

SELECT /*+ mi query */
       dname, count(*) employees
  FROM dept JOIN emp USING ( deptno )
 GROUP BY dname;

DNAME         EMPLOYEES
------------ ----------
ACCOUNTING            3
RESEARCH              5
SALES                 6

Para poder eliminar el curso del Shared Pool, primero debemos identificarlo y la forma más simple de lograrlo es consultando v$sql.

SYS@orcl > SELECT address, hash_value, executions
  2  FROM v$sql WHERE sql_text LIKE 'SELECT /*+ mi query */%';

ADDRESS  HASH_VALUE EXECUTIONS
-------- ---------- ----------
30996000 1856305823          1

Con esta información ya podemos hacer uso del procedimiento purge y luego verificamos repitiendo la consulta anterior.

SYS@orcl > exec dbms_shared_pool.purge('30996000, 1856305823','C');

PL/SQL procedure successfully completed.

SYS@orcl > /

ADDRESS  HASH_VALUE EXECUTIONS
-------- ---------- ----------
30996000 1856305823          1

¿Qué pasó? Pues que en Oracle 10gR2, no es posible usar directamente el procedimiento purge, primero hay que habilitar el evento asociado al bug que ha venido a solucionar, es decir el 5614566. Lo haremos a continuación y repetiremos el procedimiento anterior.

SYS@orcl > alter session set events '5614566 trace name context forever';

Session altered.

SYS@orcl > exec dbms_shared_pool.purge('30996000, 1856305823','C');

PL/SQL procedure successfully completed.

SYS@orcl > SELECT address, hash_value, executions
  2  FROM v$sql WHERE sql_text LIKE 'SELECT /*+ mi query */%';

no rows selected

Ahora sí funcionó todo a la perfección, logramos eliminar el cursor que deseábamos, sin tocar nada más del Shared Pool. Lo mismo se puede hacer con un procedimiento almacenado, solo que en lugar de alimentar la dirección que ocupa en el Shared Pool, es suficiente con proporcionar el nombre del mismo.

SCOTT@orcl > execute demo_prc

PL/SQL procedure successfully completed.

SYS@orcl > SELECT namespace, type, sharable_mem
  2  from v$db_object_cache
  3  where owner = 'SCOTT' and name = 'DEMO_PRC';

NAMESPACE        TYPE         SHARABLE_MEM
---------------- ------------ ------------
TABLE/PROCEDURE  PROCEDURE           13710

SYS@orcl > execute dbms_shared_pool.purge('SCOTT.DEMO_PRC','P')

PL/SQL procedure successfully completed.

SYS@orcl > /

NAMESPACE        TYPE         SHARABLE_MEM
---------------- ------------ ------------
TABLE/PROCEDURE  NOT LOADED              0

Algo similar ocurre si queremos deshacernos de una secuencia, solo basta con proporcionar el nombre del mismo.

SCOTT@orcl > select demo_seq.nextval from dual;

   NEXTVAL
----------
         1

SYS@orcl > SELECT namespace, type, sharable_mem
  2  from v$db_object_cache
  3  where owner = 'SCOTT' and name = 'DEMO_SEQ';

NAMESPACE        TYPE         SHARABLE_MEM
---------------- ------------ ------------
TABLE/PROCEDURE  SEQUENCE             1422

SYS@orcl > execute dbms_shared_pool.purge('SCOTT.DEMO_SEQ','Q')

PL/SQL procedure successfully completed.

SYS@orcl > /

NAMESPACE        TYPE         SHARABLE_MEM
---------------- -----------  ------------
TABLE/PROCEDURE  NOT LOADED              0

Con esto concluimos la presentación de este novedoso método, que de seguro nos ha de servir en muchas oportunidades, sobre todo cuando queramos deshacernos de aquellos cursores indeseables producto de algún problema con el bind peeking, tal como les comentara en un Post previo.

Para complementar lo aquí expuesto, les recomiendo la lectura del Note 457309.1 How To Flush an Object out the Library Cache, y el Note 751876.1 DBMS_SHARED_POOL.PURGE Is Not Working On 10.2.0.4.

Posts Relacionados:
Siga leyendo >>

¿Te pareció interesante este artículo?, ¿te quedaron algunas dudas?, ¿quieres sugerirme un tema a tratar?, pues déjame tus comentarios o envíame un email y para que NO te pierdas ningún Post, suscríbete por email ahora mismo!

sábado, 22 de noviembre de 2008

Hazte la vida más fácil, instrumenta tus aplicaciones

La instrumentación se refiere a la capacidad de supervisar y medir el rendimiento de una aplicación. Si nuestra aplicaciones están instrumentadas, en cualquier momento podremos saber qué rutina es la que consume más recursos, en qué porcentaje de avance está aquel proceso batch que parece no terminar, podríamos hacer trace a un módulo, independientemente del usuario que lo esté ejecutando, etc. Luego de varios años de venir realizando consultorías, he encontrado que muy pocos implementan una instrumentación de sus aplicaciones, quizás sea porque no saben que esto es posible, por ello a continuación les mostraré cómo hacerlo y los beneficios inmediatos que pueden lograrse con muy poco esfuerzo.

La pieza central para lograr la instrumentación de las aplicaciones viene a ser el package dbms_application_info. Empezaremos por conocer los valores que podemos establecer y los métodos principales que usaremos en nuestro proceso de instrumentación.

module_name
Es el nombre del módulo que está actualmente en ejecución. Se trata de una cadena de hasta 48 bytes, si es más larga se trunca silenciosamente.

action_name
Es el nombre de la acción vigente para el módulo vigente. Se trata de unan cadena de hastas 32 bytes, si es más larga se trunca silenciosamente.

set_module
Es el procedimiento que permite establecer el nombre del módulo o aplicación actual.
dbms_application_info.set_module(
   module_name=> 'Facturacion',
   action_name=> 'Obtencion.Numero.Factura' );

set_action
Es el precedimiento que permite establecer el nombre de la acción actual dentro del módulo actual.
dbms_application_info.set_action(
   action_name=> 'Calculo.Impuestos' );

set_session_longops
Es el precedimiento que permite registrar una fila en v$session_longops, como un medio para hacer seguimiento al progreso de las operaciones.
dbms_application_info.set_session_longops(
   rindex => v_rindex, --identificador de la fila en v$session_longops
   slno => v_slno, --para uso interno, no modificar
   op_name => 'Recalculo', --nombre que le asignamos a la tarea
   target_desc => 'Cheque', --descripción del objeto manipulado
   sofar => v_sofar, --indicador de cuanto trabajo ya se ha hecho
   totalwork => v_totalwork, --indicador del total de trabajo por hacer
   context => v_nro_cheque, --cualquier numero que se desee almacenar
   units => 'Cheques'); --unidad en que se representa sofar y totalwork

Veamos ahora un ejemplo en el que se combinan estos tres métodos y luego comentaremos lo que allí se hace.

DECLARE
   v_rindex    PLS_INTEGER;
   v_slno      PLS_INTEGER;
   v_totalwork NUMBER;
   v_sofar     NUMBER;
   v_obj       PLS_INTEGER;
   v_dname     dept.dname%TYPE;
BEGIN
   dbms_application_info.set_module (
      module_name => 'Listado.Empleados',
      action_name=> 'Total.Empleados' );

   v_rindex := dbms_application_info.set_session_longops_nohint;
   v_sofar := 0;

   SELECT count(*)
     INTO v_totalwork
     FROM emp;

   dbms_application_info.set_action (
      action_name=> 'Bucle.Empleados' );

   FOR c_emp IN (
      SELECT empno, ename, deptno
        FROM emp )
   LOOP
      SELECT dname INTO v_dname
        FROM dept
       WHERE deptno = c_emp.deptno;
      dbms_output.put_line(
          c_emp.empno||':'||c_emp.ename||':'||v_dname);

      v_sofar := v_sofar + 1;
      dbms_application_info.set_session_longops(
         rindex => v_rindex,
         slno => v_slno,
         op_name => 'Procesando Empleados',
         target_desc => 'Empleado',
         sofar => v_sofar,
         totalwork => v_totalwork,
         context => c_emp.empno,
         units => 'Empleados');

      dbms_lock.sleep( 2 ); --artificio para hacer lento el bucle
   END LOOP;

   dbms_application_info.set_module (
      module_name => '',
      action_name=> '' );
END;

En esta rutina se empieza por calcular el total de filas a procesar y por cada iteración vamos actualizando la fila creada en v$session_longops, de forma que podemos saber cuánto se ha avanzado y tener una estimación del tiempo restante de proceso.

SYS@orcl > SELECT message, ROUND(sofar/totalwork,2)*100 "%Avance",
  2  time_remaining falta, context empno
  3   from v$session_longops
  4  where time_remaining > 0;
MESSAGE
-----------------------------------------------------------
   %Avance      FALTA    CONTEXT
---------- ---------- ----------
Procesando Empleados: Empleado : 1 out of 14 Empleados done
         7         30       7369

SYS@orcl > /
MESSAGE
-----------------------------------------------------------
   %Avance      FALTA    CONTEXT
---------- ---------- ----------
Procesando Empleados: Empleado : 3 out of 14 Empleados done
        21         11       7521

SYS@orcl > /
MESSAGE
-----------------------------------------------------------
   %Avance      FALTA    CONTEXT
---------- ---------- ----------
Procesando Empleados: Empleado : 13 out of 14 Empleados done
        93          2       7902

SYS@orcl > /

no rows selected

También hemos identificando la rutina en sí (set_module) y las diferentes secciones al interior del mismo (set_action), esta información es visible desde algunas vistas del catálogo, como es el caso de v$sql.

SYS@orcl > SELECT action, executions, sql_text
  2  FROM v$sql
  3* WHERE module = 'Listado.Empleados';
ACTION               EXECUTIONS SQL_TEXT
-------------------- ---------- -----------------------------------------
Bucle.Empleados               1 SELECT EMPNO, ENAME, DEPTNO FROM EMP
Total.Empleados               1 SELECT COUNT(*) FROM EMP
Nombre.Departamento          14 SELECT DNAME FROM DEPT WHERE DEPTNO = :B1

Ahora la labor de hallar el origen de una sentencia SQL se simplifica pues podemos identificar rápidamente el módulo y la acción de la cual proviene, de otra forma tendriamos que enviar la sentencia SQL al equipo de desarrollo para que ellos a su vez identifiquen la aplicación de la cual proviene, labor cuyo grado de dificultad depende del nivel de control que se tenga sobre el código fuente.

Si todas nuestras aplicaciones están debidamente instrumentadas, en todo momento podemos saber cómo los diversos módulos y acciones consumen los recursos, para ellos nos valemos adicionalmente de v$active_session_history.

SYS@orcl > SELECT b.name service, NVL(module,'Sin Nombre') module,
  2  ROUND(100*(COUNT(*)/sum(COUNT(*)) OVER()), 1) pct
  3  FROM v$active_session_history a, v$services b
  4  WHERE a.service_hash = b.name_hash
  5  AND session_type <> 'BACKGROUND'
  6  AND sample_time > systimestamp - 1/288 --ultimos 5 minutos
  7  GROUP BY b.name, module
  8* ORDER BY pct desc;
SERVICE          MODULE               PCT
---------------- ------------------ ------
SYS$USERS        Facturacion           53
SYS$USERS        Almacenes           44.1
orcl.oracle.com  OEM.DefaultPool      1.9
orcl.oracle.com  OEM.Loader            .6
orcl.oracle.com  Sin Nombre            .4

Observamos el detalle a nivel de module pero también es posible obtenerlo a nivel de action.
SYS@orcl > SELECT b.name service, NVL(module,'Sin Nombre') module,
  2  NVL(action,'Sin Nombre') action,
  3  ROUND(100*(COUNT(*)/sum(COUNT(*)) OVER()), 1) pct
  4  FROM v$active_session_history a, v$services b
  5  WHERE a.service_hash = b.name_hash
  6  AND session_type <> 'BACKGROUND'
  7  AND sample_time > systimestamp - 1/288
  8  GROUP BY b.name, module, action
  9* ORDER BY pct desc;
SERVICE          MODULE           ACTION                       PCT
---------------- ---------------- -------------------------- -----
SYS$USERS        Facturacion      Calculo.Impuestos             53
SYS$USERS        Almacenes        Consulta.Inventario         44.1
orcl.oracle.com  OEM.DefaultPool  /database/instance/sitemap   1.2
orcl.oracle.com  OEM.DefaultPool  /logon                        .7
orcl.oracle.com  OEM.Loader       XMLLoader0                    .6
orcl.oracle.com  Sin Nombre       Sin Nombre                    .4

Esta información también la podemos encontrar en Enterprise Manager, pero esta vez mostrada en forma gráfica (Performance > Top Consumers).


Hasta aquí llegaremos por el momento, pero ya hemos hecho un gran avance. Usando dbms_application_info se simplifica la identificación de la aplicación a la cual pertenecen las sentencias SQL, también se posibilita saber el avance de los procesos de larga duración, de una forma muy precisa. Pero ésta no es toda la utilidad, en un próximo Post veremos beneficios adicionales de la instrumentación de las aplicaciones, estén atentos.

Posts Relacionados:
Siga leyendo >>

¿Te pareció interesante este artículo?, ¿te quedaron algunas dudas?, ¿quieres sugerirme un tema a tratar?, pues déjame tus comentarios o envíame un email y para que NO te pierdas ningún Post, suscríbete por email ahora mismo!

domingo, 16 de noviembre de 2008

¿Cómo recuperar un spfile perdido?

El server parameter file o spfile, es un archivo binario que contiene los parámetros de inicialización. Este archivo existe en el servidor de base de datos y ha venido a reemplazar al otrora parameter file o pfile. Antes de la aparición del spfile, todo cambio a los parámetros debíamos hacerlos permanentes mediante la edición del archivo pfile. Si olvidábamos hacerlo, con el siguiente inicio de la base de datos los cambios eran descartados. Con la aparición del spfile ya no es necesario preocuparse por esto, los cambios se registran automáticamente, sin necesidad de ediciones. El archivo spfile es leído al momento de iniciar la instancia, por lo que su ausencia lo impide, afortunadamente es bastante fácil superar su pérdida, veremos a continuación un par de formas de recuperarlo.


a) Usando el Alert.log
Cada vez que hacemos un cambio a un parámetro de inicialización, aparte de grabarse en el spfile, también se deja una constancia en el alert.log. Al iniciar la base de datos, en el alert.log se registra la relación de parámetros de inicialización con valores modificados. Luego, nuestra primera fuente para reconstruir un spfile perdido es desde luego el alert.log.

alertorcl.log:
. . .
Starting up ORACLE RDBMS Version: 10.2.0.4.0.
System parameters with non-default values:
  processes                = 150
  __shared_pool_size       = 125829120
  __large_pool_size        = 4194304
  __java_pool_size         = 4194304
  __streams_pool_size      = 8388608
  nls_territory            = AMERICA
  filesystemio_options     = SETALL
  sga_target               = 205520896
  control_files            = /u02/oradata/orcl/control01.ctl
  db_block_size            = 8192
  __db_cache_size          = 58720256
  compatible               = 10.2.0.4.0
  log_archive_format       = %t_%s_%r.dbf
  db_create_file_dest      = /u02/oradata
  db_recovery_file_dest    = /u01/app/oracle/flash_recovery_area
  db_recovery_file_dest_size= 4294967296
  undo_management          = AUTO
  undo_tablespace          = UNDOTBS1
  remote_login_passwordfile= EXCLUSIVE
  audit_sys_operations     = FALSE
  db_domain                = oracle.com
  job_queue_processes      = 10
  background_dump_dest     = /u01/app/oracle/admin/orcl/bdump
  user_dump_dest           = /u01/app/oracle/admin/orcl/udump
  core_dump_dest           = /u01/app/oracle/admin/orcl/cdump
  audit_file_dest          = /u01/app/oracle/admin/orcl/adump
  audit_trail              = XML, EXTENDED
  db_name                  = orcl
  open_cursors             = 300
  pga_aggregate_target     = 67108864
PMON started with pid=2, OS id=5963
. . .

Procedemos a extraer la relación de parámetros y los copiamos en un archivo, mismo que puede estar ubicado en cualquier directorio y tener cualquier nombre o extensión, pero que para este ejemplo llamaremos initorcl.ora. Una vez creado podemos construir un spfile tomándolo como insumo.

SYS@orcl> create spfile from pfile='/home/oracle/initorcl.ora';

File created.
SYS@orcl> startup;
ORACLE instance started.

Total System Global Area  205520896 bytes
Fixed Size                  1266608 bytes
Variable Size             142609488 bytes
Database Buffers           58720256 bytes
Redo Buffers                2924544 bytes
Database mounted.
Database opened.

Acá estamos asumiendo que la base de datos estaba abajo, pero si aún está operativa entonces tendremos que hacerlo con un paso extra.

SYS@orcl> create spfile from pfile='/home/oracle/initorcl.ora';
create spfile from pfile='/home/oracle/initorcl.ora'
*
ERROR at line 1:
ORA-32002: cannot create SPFILE already being used by the instance

SYS@orcl> create spfile='?/dbs/spfileorcl.bak' from pfile='/home/oracle/initorcl.ora';

File created.

$ mv $ORACLE_HOME/dbs/spfileorcl.bak $ORACLE_HOME/dbs/spfileorcl.ora

El resultado final es un nuevo spfile que incluye los cambios más recientes.


b) Usando backups obtenidos con RMAN
Si por alguna razón no podemos hacer uso del alert.log, nuestra siguiente alternativa es recurrir a los backups obtenidos con RMAN. Cada vez que obtenemos un backup que, de forma directa o indirecta, involucre al tablespace System, tanto el controlfile como el spfile son automáticamente respaldados. Esto también ocurre si hemos configurado RMAN con "controlfile autobackup on", en este caso no importa si el backup incluye o no al tablespace System. Asumiendo que la base de datos está operativa, el procedimiento a seguir para restaurar el spfile es:

RMAN> restore spfile to '?/dbs/spfileora.bak' from autobackup;

Starting restore at 05/11/2008 17:42:25
allocated channel: ORA_DISK_1
channel ORA_DISK_1: sid=143 devtype=DISK

recovery area destination: /u01/app/oracle/flash_recovery_area
database name (or database unique name) used for search: ORCL
channel ORA_DISK_1: autobackup found in the recovery area
channel ORA_DISK_1: autobackup found: /u01/app/oracle/flash_recovery_area/ORCL/autobackup/2008_11_05/o1_mf_s_670006939_4k45zd4k_.bkp
channel ORA_DISK_1: SPFILE restore from autobackup complete
Finished restore at 05/11/2008 17:42:29

$ mv $ORACLE_HOME/dbs/spfileorcl.bak $ORACLE_HOME/dbs/spfileorcl.ora

Si la base de datos está abajo, el procedimiento es algo distinto.

RMAN> startup nomount;

startup failed: ORA-01078: failure in processing system parameters
LRM-00109: could not open parameter file '/u01/app/oracle/product/10.2.0/db_1/dbs/initorcl.ora'

starting Oracle instance without parameter file for retrival of spfile
Oracle instance started

Total System Global Area     159383552 bytes

Fixed Size                     1266344 bytes
Variable Size                 54529368 bytes
Database Buffers             100663296 bytes
Redo Buffers                   2924544 bytes

RMAN> restore spfile  from autobackup
2> recovery area '/u01/app/oracle/flash_recovery_area'
3> db_name 'ORCL';

Starting restore at 05/11/2008 17:49:50
using channel ORA_DISK_1

recovery area destination: /u01/app/oracle/flash_recovery_area
database name (or database unique name) used for search: ORCL
channel ORA_DISK_1: autobackup found in the recovery area
channel ORA_DISK_1: autobackup found: /u01/app/oracle/flash_recovery_area/ORCL/autobackup/2008_11_05/o1_mf_s_670006939_4k45zd4k_.bkp
channel ORA_DISK_1: SPFILE restore from autobackup complete
Finished restore at 05/11/2008 17:49:53

Observamos que RMAN es capaz de iniciar la instancia, aún cuando no existe el spfile, justamente porque asume que queremos restaurarlo de algún backup. Para lograr restaurar el spfile, debemos indicar la ubicación del Recovery Area, así como el nombre de la base de datos, con esos datos RMAN busca el backup más reciente.

En conclusión, el archivo spfile es requerido para iniciar la instancia, si lo hemos perdido podemos recurrir al alert.log o a los backups. En caso de que no podamos lograrlo, siempre queda como última alternativa el crear un pfile con el mínimo de parámetros y a partir de él un spfile, pero es una situación en la que difícilmente deberíamos caer si hemos sido suficientemente cuidadosos y precavidos.

Puedes complementar lo acá detallado, leyendo el Note 372996.1 Using RMAN to Restore and Recover a Database When the Repository and Spfile/Init.ora Files Are Also Lost.

Siga leyendo >>

¿Te pareció interesante este artículo?, ¿te quedaron algunas dudas?, ¿quieres sugerirme un tema a tratar?, pues déjame tus comentarios o envíame un email y para que NO te pierdas ningún Post, suscríbete por email ahora mismo!

jueves, 13 de noviembre de 2008

Postergando la validación de los constraints.

Usamos Oracle para tener nuestra información segura y accesible, pero si ésta no es consistente pierde su valor. Por esta razón normalmente implementamos un mínimo de reglas que deben cumplir nuestros datos, esto mediante el uso de constraints. A saber son: not null, unique, primary key, foreign key y check; por defecto estas restricciones se validan en cuanto tratamos de modificar la información, lo cual en ocasiones nos puede limitar y obligarnos a tomar caminos alternativos, que pueden pasar incluso por deshabilitar los constraints de forma temporal. Esto introduce la posibilidad de generar información inconsistente, afortunadamente existe una forma de postergar estas validaciones, sin sacrificar la integridad de la información, y que aún sigue siendo poco conocida.

Para lograrlo nuestro objetivo de violar temporalmente las reglas impuestas por los constraints, debemos crearlos de una forma especial, conocida como deferrable constraints. Veamos la diferencia con el siguiente ejemplo, en el cual se crea un foreign key constraint con validación inmediata y luego con la modalidad postergada.

transaccion.sql
BEGIN
  INSERT INTO emp (
     empno, ename, job, mgr, hiredate, sal, comm, deptno )
  VALUES (
     8000, 'BROWN', 'MANAGER', NULL,
     TO_DATE('15-JAN-1982', 'DD-MON-YYYY'), 3300, NULL, 50);
  INSERT INTO dept ( deptno, dname, loc )
  VALUES ( 50, 'MARKETING', 'MIAMI');
  COMMIT;
END;
/

SCOTT@orcl > ALTER TABLE emp ADD CONSTRAINT emp_fk_dept
  2  FOREIGN KEY(deptno) REFERENCES dept;

Table altered.

SCOTT@orcl > @transaccion
BEGIN
*
ERROR at line 1:
ORA-02291: integrity constraint (SCOTT.EMP_FK_DEPT) violated - parent key not found
ORA-06512: at line 2

Aún cuando el resultado final del bloque BEGIN/END es información consistente, la inmediatez de la validación impide ingresar una fila en la table emp referenciando a un deptno aún no existente en la tabla dept. Redefinamos entonces el constraint para que sea postergable.

SCOTT@orcl > ALTER TABLE emp ADD CONSTRAINT emp_fk_dept
  2  FOREIGN KEY(deptno) REFERENCES dept
  3  DEFERRABLE INITIALLY DEFERRED;

Table altered.

SCOTT@orcl > @transaccion
PL/SQL procedure successfully completed.

Ahora sí se permite concluir la transacción, la validación se ha postergado al momento de hacer el commit, si en ese momento aún subsistiese alguna inconsistencia entonces recién se aborta la transacción.

SCOTT@orcl > DELETE dept WHERE deptno = 50;

1 row deleted.

SCOTT@orcl > commit;
commit
*
ERROR at line 1:
ORA-02091: transaction rolled back
ORA-02292: integrity constraint (SCOTT.EMP_FK_DEPT) violated - child record found

Si bien permitir que la validación se postergue al momento de concluir la transacción resulta útil, por una cuestión de orden resulta más conveniente que ésta no sea la conducta por defecto, sino más bien la excepción, para ello debemos crear el constraint de otra forma.

SCOTT@orcl > ALTER TABLE emp ADD CONSTRAINT emp_fk_dept
  2  FOREIGN KEY(deptno) REFERENCES dept
  3  DEFERRABLE INITIALLY IMMEDIATE;

Como su nombre lo sugiere, IMMEDIATE conlleva a la validación del constraint en forma simultánea con la modificación de los datos, pero al ser el constraint DEFERRABLE, se permite postergar la validación para casos especiales, que se habilitan con una sintaxis adicional. Veámoslo con el ejemplo.

SCOTT@orcl > @transaccion
BEGIN
*
ERROR at line 1:
ORA-02291: integrity constraint (SCOTT.EMP_FK_DEPT) violated - parent key not found
ORA-06512: at line 2

SCOTT@orcl > set constraint emp_fk_dept deferred;

Constraint set.

SCOTT@orcl > @transaccion

PL/SQL procedure successfully completed.

Con la inclusión de set constraint .. deferred, habilitamos temporalmente, y sólo para las transacciones al interior de la sesión vigente, la postergación del constraint "emp_fk_dept". Podemos consignar toda una lista de constraints a postergar temporalmente o en su defecto indicar que lo sean todos los que puedan tratar de validarse en las futuras transacciones (siempre que hayan sido creadas como deferrable).

set constraint constraint_1, constraint_2, ..., constraint_n deferred;

set constraint all deferred;

Tenemos entonces en los deferrable constraints, una forma soportada de violar temporalmente las reglas que normalmente son validadas de inmediato, postergándolas hasta el momento en que concluímos la transacción (commit). Todo constraint puede ser postergado, pero en el caso particular del primary key y del unique, los índices que sirven para validar el cumplimiento de estos constraints son creados como índices no únicos, cuando normalmente son únicos. Esto implica que ocuparán algo más de espacio y su mantenimiento requerirá de algo más de CPU, pero usualmente en tan pequeña magnitud que puede ser despreciado si lo comparamos con los eventuales beneficios.

Puedes leer más del tema en el manual Database Concepts y en el Note 73647.1 Deferred Constraints Example, también les recomiendo leer este Post por Richard Foote, en el que expone algunas razones por las que puede resultar inconveniente crear algunos tipos de constraints como postergables.

Siga leyendo >>

¿Te pareció interesante este artículo?, ¿te quedaron algunas dudas?, ¿quieres sugerirme un tema a tratar?, pues déjame tus comentarios o envíame un email y para que NO te pierdas ningún Post, suscríbete por email ahora mismo!

martes, 11 de noviembre de 2008

¿Cómo importar datos sin un dump de por medio?

Todos hemos usado alguna vez los utilitarios export e import, pero con la aparición de Oracle10g vino como novedad Data Pump. Aún muchos no se han animado a usarlo, pero eventualmente tendrán que hacerlo ya que Oracle ha anunciado que en futuras versiones sólo se proporcionará import. Pues bien, la verdad es que si investigamos un poco y empezamos a usarlo, notaremos que mucho de los problemas por lo que pasamos al tratar de hacer algunas cosas medio rebuscadas con export e import, ahora son posibles con Data Pump y se los iré mostrando poco a poco, pero para empezar veamos cómo ahora es posible exportar los datos de un base de datos e importarlos en otra remota sin necesidad de crear un dump, e incluso crear allí el usuario destino si es que éste aún no existe, a ver ¡traten de hacer eso con export e import!

Para hacer esta demostración contaremos con una base de datos 10.2 bajo Solaris 10 como origen, y otra base de datos 11.1 bajo RHEL 4.7 como destino. Debemos empezar por crear un database link en la base de datos destino que nos permita comunicarnos con la base de datos origen

SYSTEM@orcl > create database link solaris
  2  connect to system identified by oracle
  3  using 'solaris';

Database link created.

SYSTEM@orcl > select banner from v$version@solaris;

BANNER
----------------------------------------------------------------
Oracle Database 10g Enterprise Edition Release 10.2.0.2.0 - Prod
PL/SQL Release 10.2.0.2.0 - Production
CORE    10.2.0.2.0      Production
TNS for Solaris: Version 10.2.0.2.0 - Production
NLSRTL Version 10.2.0.2.0 - Production

Ya tenemos el database link operativo, procedamos a deshacernos del usuario scott que usualmente se instala como parte de los ejemplos.

SYSTEM@orcl > drop user scott cascade;

User dropped.

Hagamos la importación de los datos.
[oracle@linux ~]$ impdp system/oracle nologfile=y network_link=solaris schemas=scott

Import: Release 11.1.0.6.0 - Production on Tuesday, 11 November, 2008 9:58:03

Copyright (c) 2003, 2007, Oracle.  All rights reserved.

Connected to: Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 - Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options
Starting "SYSTEM"."SYS_IMPORT_SCHEMA_01":  system/******** nologfile=y network_link=solaris schemas=scott
Estimate in progress using BLOCKS method...
Processing object type SCHEMA_EXPORT/TABLE/TABLE_DATA
Total estimation using BLOCKS method: 192 KB
Processing object type SCHEMA_EXPORT/USER
Processing object type SCHEMA_EXPORT/SYSTEM_GRANT
Processing object type SCHEMA_EXPORT/ROLE_GRANT
Processing object type SCHEMA_EXPORT/DEFAULT_ROLE
Processing object type SCHEMA_EXPORT/PRE_SCHEMA/PROCACT_SCHEMA
Processing object type SCHEMA_EXPORT/TABLE/TABLE
. . imported "SCOTT"."DEPT"                                   4 rows
. . imported "SCOTT"."EMP"                                   14 rows
. . imported "SCOTT"."SALGRADE"                               5 rows
. . imported "SCOTT"."BONUS"                                  0 rows
Processing object type SCHEMA_EXPORT/TABLE/INDEX/INDEX
Processing object type SCHEMA_EXPORT/TABLE/CONSTRAINT/CONSTRAINT
Processing object type SCHEMA_EXPORT/TABLE/INDEX/STATISTICS/INDEX_STATISTICS
Processing object type SCHEMA_EXPORT/TABLE/CONSTRAINT/REF_CONSTRAINT
Processing object type SCHEMA_EXPORT/TABLE/STATISTICS/TABLE_STATISTICS
Job "SYSTEM"."SYS_IMPORT_SCHEMA_01" successfully completed at 09:58:17

No hay mensajes de error, pero igual comprobaremos.

[oracle@linux ~]$ sqlplus scott/tiger

SQL*Plus: Release 11.1.0.6.0 - Production on Tue Nov 11 09:58:23 2008

Copyright (c) 1982, 2007, Oracle.  All rights reserved.


Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 - Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options

SCOTT@orcl > select table_name, num_rows
  2  from user_tables;

TABLE_NAME                       NUM_ROWS
------------------------------ ----------
SALGRADE                                5
BONUS                                   0
EMP                                    14
DEPT                                    4

Todo funcionó de acuerdo a lo esperado, hemos logrado sin mayor problema:
  • Usar un cliente de una versión superior (11g) para extraer datos de una versión inferior(10gR2), algo que con exp usualmente nos daría algunos errores de compatibilidad.
  • Creación del usuario receptor de los datos, esto es posible porque hemos empleado un usuario privilegiado, system en este caso, de otra forma el usuario debe existir previamente.
  • Transferir los datos desde una base de datos remota sin necesidad de generar un archivo dump como paso intermedio, gracias a la inclusión del parámetro network_link.

Puedes encontrar información adicional en el Note 351598.1 Export/Import DataPump: The Minimum Requirements to Use Export DataPump and Import DataPump (System Privileges) y en la documentación oficial.

Siga leyendo >>

¿Te pareció interesante este artículo?, ¿te quedaron algunas dudas?, ¿quieres sugerirme un tema a tratar?, pues déjame tus comentarios o envíame un email y para que NO te pierdas ningún Post, suscríbete por email ahora mismo!

lunes, 10 de noviembre de 2008

¿Cómo acelerar el acceso a los file systems?

Estas usando file systems para tu base de datos y todo opera en aparente normalidad, pero ¿sabías que por defecto Oracle no aprovecha todas las mejoras que los file systems modernos tienen implementados? Pues esa es la cruda realidad, si te interesa saber cómo puedes sacarle el jugo al I/O de tus discos con solo cambiar un parámetro, entonces debemos empezar por algo de teoría no Oracle.


File Buffer Cache
Un archivo no es más que una colección de bits almacenados en un medio persistente. Cuando un proceso requiere acceder a los datos de un archivo, el sistema operativo los lleva a la memoria principal, donde el proceso puede hacer uso del mismo, modificarlo, y luego solicitar que sea nuevamente guardado en disco. Pero teniendo en mente que los discos son mucho más lentos que la memoria principal, los sistemas operativos hacen normalmente uso de un buffer en memoria, llamado file buffer cache. Como resultado, el sistema operativo primero intenta obtener los datos del buffer cache, si no la encuentra allí la lee de disco y la coloca en el buffer cache. De forma similar, las escrituras también pasan por el buffer cache de forma que las futuras lecturas puedan ser satisfechas sin necesidad de acceder a los discos.

Direct I/O
Hasta acá el uso del file buffer cache parecería beneficioso, pero no olvidemos que Oracle ya tiene su propia implementación: el database buffer cache. Al ser el propio Oracle quien controla qué bloques requieren de permanecer o no en el cache, bajo un algoritmo LRU con el que no cuenta el sistema operativo, la existencia del file buffer cache puede resultar indeseable e innecesario al tener que viajar los datos primero al file buffer cache y luego al database buffer cache, conllevando a un consumo adicional de CPU y también de memoria principal, la cual ya no está disponible para Oracle. Tomando esto en cuenta, hace su aparición el Direct I/O, como una forma de evitar el uso del file buffer cache, de hecho es la forma con que se interactúa con los raw devices (porciones de disco no formateados); en la actualidad prácticamente todos los sistemas operativos soportan direct I/O y en algunos casos incluso versiones más sofisticadas como concurrent i/o provisto por IBM con JFS2.

Habilitando Direct I/O
En Oracle existe una forma de controlar la forma en que se interactúa con los file systems, se trata de filesystemio_options, que acepta como valores:
  • none. La grabación será síncrona con bloqueo, es decir la aplicación espera a que la llamada al sistema se complete antes de poder hacer una nueva llamada. Es universalmente soportada pero es la más forma más lenta.
  • asynch. La aplicación no espera a que la llamada al sistema se complete, puede realizar otras tareas mientras espera la confirmación de la llamada previa.
  • directio. La grabación es síncrona sin pasar por el file buffer cache (las dos modalidades previas hacen uso del file buffer cache).
  • setall. La grabación es asíncrona y sin pasar por el file buffer cache. Presenta la posibilidad de máximo desempeño.

Dependiendo de la plataforma Oracle le asigna a éste parámetro el valor por defecto none (ejm. Linux) o asynch (ejm. Solaris), para habilitar direct I/O los valores a escoger han de ser directio o mejor aún setall (asynch + directio). Este parámetro no se puede cambiar online por lo que luego de aplicar el cambio en el spfile.ora debes reiniciar la base de datos para que entre en efecto.

SYS@orcl > show parameter filesystemio_options

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
filesystemio_options                 string      none

SYS@orcl > alter system set filesystemio_options=setall scope=spfile;

System altered.

SYS@orcl > shutdown immediate
Database closed.
Database dismounted.
ORACLE instance shut down.

SYS@orcl > startup
ORACLE instance started.

Total System Global Area  209715200 bytes
Fixed Size                  1279360 bytes
Variable Size             109054592 bytes
Database Buffers           96468992 bytes
Redo Buffers                2912256 bytes
Database mounted.
Database opened.

Estamos probando con Linux, de allí el valor none, ahora para verificar que estamos trabajando con asynch y directio en simultáneo, podemos valernos de algunos utilitarios propios de Linux, pero que tienen sus similares en otros sistemas operativos. Primero veamos la situación original, es decir cuando filesystemio_options=none.

SYS@orcl> shutdown immediate
Database closed.
Database dismounted.
ORACLE instance shut down.
SYS@orcl> startup mount;
ORACLE instance started.

Total System Global Area  205520896 bytes
Fixed Size                  1266608 bytes
Variable Size             142609488 bytes
Database Buffers           58720256 bytes
Redo Buffers                2924544 bytes
Database mounted.

$ ps -ef | grep dbw
oracle   28532     1  0 16:56 ?        00:00:00 ora_dbw0_orcl
SYS@orcl> alter database open;

Database altered.
$ more /tmp/trace_dbwr.out
. . .
open("/u02/oradata/ORCL/datafile/o1_mf_system_3wqn5ypm_.dbf", O_RDWR|O_SYNC|O_LARGEFILE) = 17
. . .

$ cat /proc/slabinfo | grep kio
kioctx   18  30  256  15  1 : tunables  120  60  0 : slabdata  2  2  0
kiocb     0   0  128  31  1 : tunables  120  60  0 : slabdata  0  0  0

Ahora con filesystemio_options=setall.

$ more /tmp/trace_dbwr.out
. . .
open("/u02/oradata/ORCL/datafile/o1_mf_system_3wqn5ypm_.dbf", O_RDONLY|O_DIRECT|O_LARGEFILE) = 17
. . .
$ cat /proc/slabinfo | grep kio
kioctx   17  30  256  15  1 : tunables  120  60  0 : slabdata  2  2 0
kiocb     7  31  128  31  1 : tunables  120  60  0 : slabdata  1  1 0

Que DBWR abra los datafiles con O_DIRECT es señal del uso de Direct I/O, mientras que la presencia de valores distintos de cero para kiocb lo es del uso de Asynch I/O.

¿File buffer cache o Direct I/O?
La decisión no es simple, antes de optar por uno u otro debemos tener en cuenta cómo puede afectar a nuestras aplicaciones, para empezar vemos el caso de una aplicación simulada que es intensiva en lecturas aleatorias, lo cual es típico de las aplicaciones OLTP.

Random.sh
sqlplus / as sysdba << EOF
alter system flush buffer_cache;
exit;
EOF
sqlplus /nolog @Random.sql &
sqlplus /nolog @Random.sql &
sqlplus /nolog @Random.sql &
sqlplus /nolog @Random.sql &

Random.sql
connect test/test
set timing on
alter session set events '10046 trace name context forever, level 8';
begin
  for i in 1..200000 loop
     execute immediate
        'select data from testio where id = :id'
     using ROUND(dbms_random.value(1,200000));
  end loop;
end;
/
exit

Analizando los tiempos con tkprof:

filesystemio_options=none

select data
from
 testio where id = :id

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        4      0.00       1.38          0          0          0           0
Execute 800000     51.29     172.13          1          1          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   800004     51.30     173.51          1          1          0           0

===
filesystemio_options=setall

select data
from
 testio where id = :id

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        4      0.00       0.00          0          0          0           0
Execute 800000     38.84     128.18          0          0          0           0
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total   800004     38.85     128.18          0          0          0           
0

Ahora con una aplicación intensiva en Full Table Scans, típico de las aplicaciones DSS.

Full.sh
sqlplus / as sysdba << EOF
alter system flush buffer_cache;
exit;
EOF
sqlplus /nolog @Full.sql &
sqlplus /nolog @Full.sql &
sqlplus /nolog @Full.sql &
sqlplus /nolog @Full.sql &

Full.sql
connect test/test
set timing on
alter session set events '10046 trace name context forever, level 8';
declare v_data testio.data%type;
begin
for i in 1..25 loop
select max(data) into v_data from testio;
end loop;
end;
/
exit

Resumen de traces analizados con tkprof.

filesystemio_options=none

SELECT MAX(DATA)
FROM
 TESTIO

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        4      0.00       0.00          0          0          0           0
Execute    100      0.01       0.01          0          0          0           0
Fetch      100     61.89     232.97     503813    1446600          0         100
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      204     61.91     232.99     503813    1446600          0         100

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  db file sequential read                       146        0.00          0.00
  db file scattered read                      13771        0.67         19.53 

====
filesystemio_options=setall

SELECT MAX(DATA)
FROM
 TESTIO

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        4      0.01       1.22          1          1          0           0
Execute    100      0.02       0.06          0          0          0           0
Fetch      100     70.63     366.62     259438    1446600          0         100
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      204     70.66     367.91     259439    1446601          0         100

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  read by other session                       20820        0.24        133.64
  db file scattered read                       8942        0.99        100.00 
  latch: cache buffers chains                   193        0.02          0.73
  db file sequential read                       302        0.07          1.39

Con filesystemio_options=setall las lecturas aleatorias mejoraron en 26%, mientras que los Full Table Scans empeoraron en casi 58%!

Conclusiones
Revisando los resultados de los escenarios simulados, podemos concluir que, ante la activación de direct I/O, las aplicaciones intensivas en acceso aleatorio a los discos se ven beneficiadas mientras que las intensivas en Full Table Scans se ven perjudicadas, de allí que no debemos saltar a la conclusión de que con solo habilitar el direct I/O nuestra base de datos será mágicamente más rápida, es posible que así sea, como también es posible que el impacto sea nulo e incluso adverso, primero hagan las pruebas del caso durante un periodo de carga típica antes de decidir dejarlo permanentemente. Lo que sí es seguro que mejorará el desempeño es el uso de filesystemio_options=asynch, por lo que es lo mínimo con lo que deberías configurar tu base de datos.

Cada plataforma tiene sus particularidades en cuanto a la aplicabilidad de los diversos valores para filesystemio_options e incluso hay algunos bugs específicos para ciertas combinaciones de versión de Oracle + versión de Sistema Operativo, por ello es conveniente que te informes más del tema. Te recomiendo la lectura de los Notes 462072.1 File System's Buffer Cache versus Direct I/O, 555601.1 How To Verify Whether DIRECTIO is Being Used, 237299.1 How To Check if Asynchronous I/O is Working On Linux y 432854.1 Asynchronous I/O Support on OCFS/OCFS2 and Related Settings: filesystemio_options, disk_asynch_io, finalmente Boost application performance using asynchronous I/O.

Post Relacionados:

Siga leyendo >>

¿Te pareció interesante este artículo?, ¿te quedaron algunas dudas?, ¿quieres sugerirme un tema a tratar?, pues déjame tus comentarios o envíame un email y para que NO te pierdas ningún Post, suscríbete por email ahora mismo!

domingo, 9 de noviembre de 2008

ORA-00257 archiver error, ¿aún cuando ya hice espacio?

Conscientes de que Oracle9i está de salida, la empresa Acme finalmente hizo la actualización a la versión 10gR2; las cosas no podían ir mejor hasta que algunos usuarios empiezan a reportar que sus sesiones están suspendidas y otros que no pueden conectarse, obteniendo el error ORA-00257: archiver error. Connect internal only, until freed.

El problema resulta evidente, ya antes había pasado, se toma la rápida decisión de mover algunos archivelogs antiguos a otro directorio; ahora hay espacio pero el problema parece no resolverse, ¿qué puede estar pasando? Pues que ahora están usando Flash Recovery Area (FRA) y eso cambia las cosas.

Con Oracle 10g aparece Flash Recovery Area como un espacio en el cual se depositan por defecto los archivelogs y backups, Oracle se encarga de gestionarlo y exige que su mantenimiento se haga sin recurrir a comandos del Sistema Operativo.

Para este caso en particular, como la liberación de espacio se ha hecho mediante el traslado de archivelogs a otro directorio usando el Sistema Operativo, Oracle permanece sin ser notificado de tal cambio y por tanto asume que el problema de falta de espacio persiste, y así nos lo hace saber mediante mensajes en el alert.ora.

Sat Nov  8 13:58:46 2008
Thread 1 advanced to log sequence 26
Current log# 2 seq# 18 mem# 0: /u01/oradata/orcl/redo02.log
Sat Nov  8 13:58:46 2008
Errors in file /opt/oracle/admin/orcl/bdump/orcl_arc0_21093.trc:
ORA-19815: WARNING: db_recovery_file_dest_size of 209715200 bytes is 100.00% used, and has 0 remaining bytes available.
Sat Nov  8 13:58:46 2008
************************************************************************
You have following choices to free up space from flash recovery area:
1. Consider changing RMAN RETENTION POLICY. If you are using Data Guard,
then consider changing RMAN ARCHIVELOG DELETION POLICY.
2. Back up files to tertiary device such as tape using RMAN
BACKUP RECOVERY AREA command.
3. Add disk space and increase db_recovery_file_dest_size parameter to
reflect the new space.
4. Delete unnecessary files using RMAN DELETE command. If an operating
system command was used to delete files, then use RMAN CROSSCHECK and
DELETE EXPIRED commands.
************************************************************************
Sat Nov  8 13:58:46 2008
Errors in file /opt/oracle/admin/orcl/bdump/orcl_arc0_21093.trc:
ORA-19809: limit exceeded for recovery files
ORA-19804: cannot reclaim 51228672 bytes disk space from 209715200 limit
ARC0: Error 19809 Creating archive log file to '/fra/ORCL/archivelog/2008_11_08/o1_mf_1_25_0_.arc'
ARC0: Failed to archive thread 1 sequence 25 (19809)
ARCH: Archival stopped, error occurred. Will continue retrying
Sat Nov  8 13:58:46 2008
ORACLE Instance orcl - Archival Error
Sat Nov  8 13:58:46 2008
ORA-16038: log 1 sequence# 25 cannot be archived
ORA-19809: limit exceeded for recovery files
ORA-00312: online log 1 thread 1: '/u01/oradata/orcl/redo01.log'

Como podemos observar, Oracle encuentra el problema y no solo lo reporta sino que también nos da un resumen de las acciones que podemos tomar para superar el problema. La alternativa (4) es reveladora, si queremos hacer espacio eliminando archivos debemos usar el comando delete de RMAN para lograrlo.

RMAN> list archivelog all;


List of Archived Log Copies
Key     Thrd Seq     S Low Time            Name
------- ---- ------- - ------------------- ----
18      1    23      A 08/11/2008 13:20:58 /fra/ORCL/archivelog/2008_11_08/o1_mf_1_23_4kdsx5y2_.arc
19      1    24      A 08/11/2008 13:31:33 /fra/ORCL/archivelog/2008_11_08/o1_mf_1_24_4kdsx6th_.arc
20      1    25      A 08/11/2008 13:45:11 /fra/ORCL/archivelog/2008_11_08/o1_mf_1_25_4kdc7vrd_.arc

RMAN> delete noprompt archivelog until sequence 24;

released channel: ORA_DISK_1
allocated channel: ORA_DISK_1
channel ORA_DISK_1: sid=142 devtype=DISK

List of Archived Log Copies
Key     Thrd Seq     S Low Time            Name
------- ---- ------- - ------------------- ----
18      1    23      A 08/11/2008 13:20:58 /fra/ORCL/archivelog/2008_11_08/o1_mf_1_23_4kdsx5y2_.arc
19      1    24      A 08/11/2008 13:31:33 /fra/ORCL/archivelog/2008_11_08/o1_mf_1_24_4kdsx6th_.arc
deleted archive log
archive log filename=/fra/ORCL/archivelog/2008_11_08/o1_mf_1_23_4kdsx5y2_.arc recid=18 stamp=670289494
deleted archive log
archive log filename=/fra/ORCL/archivelog/2008_11_08/o1_mf_1_24_4kdsx6th_.arc recid=19 stamp=670289494
Deleted 2 objects

Volviendo a nuestro escenario, intentamos eliminar los archivelogs pero el esfuerzo es en vano.

RMAN> delete noprompt archivelog until sequence 24;

released channel: ORA_DISK_1
allocated channel: ORA_DISK_1
channel ORA_DISK_1: sid=142 devtype=DISK

List of Archived Log Copies
Key     Thrd Seq     S Low Time            Name
------- ---- ------- - ------------------- ----
18      1    23      A 08/11/2008 13:20:58 /fra/ORCL/archivelog/2008_11_08/o1_mf_1_23_4kdsx5y2_.arc
19      1    24      A 08/11/2008 13:31:33 /fra/ORCL/archivelog/2008_11_08/o1_mf_1_24_4kdsx6th_.arc

RMAN-06207: WARNING: 2 objects could not be deleted for DISK channel(s) due
RMAN-06208:          to mismatched status.  Use CROSSCHECK command to fix status
RMAN-06210: List of Mismatched objects
RMAN-06211: ==========================
RMAN-06212:   Object Type   Filename/Handle
RMAN-06213: --------------- ---------------------------------------------------
RMAN-06214: Archivelog      /fra/ORCL/archivelog/2008_11_08/o1_mf_1_23_4kdsx5y2_.arc
RMAN-06214: Archivelog      /fra/ORCL/archivelog/2008_11_08/o1_mf_1_24_4kdsx6th_.arc

Nuevamente junto con el mensaje se nos proporciona la solución: hacer un crosscheck.

RMAN> crosscheck archivelog all;

released channel: ORA_DISK_1
allocated channel: ORA_DISK_1
channel ORA_DISK_1: sid=142 devtype=DISK
validation failed for archived log
archive log filename=/fra/ORCL/archivelog/2008_11_08/o1_mf_1_23_4kdsx5y2_.arc recid=18 stamp=670288851
validation failed for archived log
archive log filename=/fra/ORCL/archivelog/2008_11_08/o1_mf_1_24_4kdsx6th_.arc recid=19 stamp=670288858
Crosschecked 2 objects

Finalmente removemos del catálogo de RMAN los archivelogs reportados como no encontrados en el paso anterior.

RMAN> delete noprompt expired archivelog all;

released channel: ORA_DISK_1
allocated channel: ORA_DISK_1
channel ORA_DISK_1: sid=142 devtype=DISK

List of Archived Log Copies
Key     Thrd Seq     S Low Time            Name
------- ---- ------- - ------------------- ----
18      1    23      X 08/11/2008 13:20:58 /fra/ORCL/archivelog/2008_11_08/o1_mf_1_23_4kdsx5y2_.arc
19      1    24      X 08/11/2008 13:31:33 /fra/ORCL/archivelog/2008_11_08/o1_mf_1_24_4kdsx6th_.arc
deleted archive log
archive log filename=/fra/ORCL/archivelog/2008_11_08/o1_mf_1_23_4kdsx5y2_.arc recid=18 stamp=670288851
deleted archive log
archive log filename=/fra/ORCL/archivelog/2008_11_08/o1_mf_1_24_4kdsx6th_.arc recid=19 stamp=670288858
Deleted 2 EXPIRED objects

Revisamos el alert.log y el problema aparece como resuelto.

Sat Nov  8 14:21:03 2008
ARC1: Archiving not possible: No primary destinations
ARC1: Failed to archive thread 1 sequence 26 (4)
Sat Nov  8 14:21:04 2008
db_recovery_file_dest_size of 200 MB is 27.83% used. This is a
user-specified limit on the amount of space that will be used by this
database for recovery-related files, and does not reflect the amount of
space available in the underlying filesystem or ASM diskgroup.
Sat Nov  8 14:21:15 2008
Archiver process freed from errors. No longer stopped
Sat Nov  8 14:21:15 2008
Thread 1 advanced to log sequence 27
  Current log# 1 seq# 20 mem# 0: /u01/oradata/orcl/redo03.log

Las sesiones vuelven a la vida y los usuarios pueden iniciar nuevas conexiones, todo ha regresado a la normalidad; podemos finalmente respirar con alivio y nos queda la enseñanza de que si bien usar Flash Recovery Area es altamente recomendado, debemos tener presente no manipular sus contenidos por medios ajenos a los proporcionados por Oracle.

Si deseas profundizar en el tema, te recomiendo leer el Note 278308.1 How to Resolve ORA-00257: Archiver is Stuck Error in 10g? y el Note 315098.1 How is the space pressure managed in the Flash Recovery Area - An Example.

Posts Relacionados:
Siga leyendo >>

¿Te pareció interesante este artículo?, ¿te quedaron algunas dudas?, ¿quieres sugerirme un tema a tratar?, pues déjame tus comentarios o envíame un email y para que NO te pierdas ningún Post, suscríbete por email ahora mismo!

jueves, 6 de noviembre de 2008

¿Cómo renombrar con Rman datafiles nombrados con caracteres especiales?

Faltaba espacio en un tablespace y optaste por agregar un datafile nuevo, resuelto el problema regresas a tus asuntos y te olvidas del tema. Luego, mientras de forma rutinaria inspeccionas los contenidos del filesystem, reparas en que el nuevo datafile tiene un nombre algo raro, tiene caracteres especiales, recuerdas que al escribir el nombre lo hiciste con tanta prisa que te equivocaste un par de veces pero al final el nombre estaba OK, o al menos en tu pantalla así parecía; bueno pues, igual hay que resolver el problema, pero ¿por dónde empezar?

La primera intención es renombrar el archivo desde el sistema operativo, pero encuentras problemas para lograrlo, ¿por qué no ayudarnos de nuestro viejo conocido Rman? Afortunadamente el procedimiento es bastante simple:
  1. Primero invocamos Rman y verificamos la situación inicial.
    $ rman target /
    RMAN> report schema;
    
    using target database control file instead of recovery catalog
    Report of database schema
    
    List of Permanent Datafiles
    ===========================
    File Size(MB) Tablespace       RB segs Datafile Name
    ---- -------- ---------------- ------- ------------------------
    1    480      SYSTEM           ***     /u01/oradata/orcl/system01.dbf
    2    30       UNDOTBS1         ***     /u01/oradata/orcl/undotbs01.dbf
    3    230      SYSAUX           ***     /u01/oradata/orcl/sysaux01.dbf
    4    5        USERS            ***     /u01/oradata/orcl/users01.dbf
    5    100      EXAMPLE          ***     /u01/oradata/orcl/example01.dbf
    6    10       DEMO             ***     /u01/oradata/orcl/�¡Ã³demo01.dbf
    
    List of Temporary Files
    =======================
    File Size(MB) Tablespace           Maxsize(MB) Tempfile Name
    ---- -------- -------------------- ----------- --------------------
    1    20       TEMP                 32767       /u01/oradata/orcl/temp01.dbf
  2. Procedemos a poner el tablespace afectado fuera de linea.
    RMAN> sql "alter tablespace demo offline";
  3. Obtenemos una copia del datafile, referenciándolo por su file#.
    RMAN> copy datafile 6 to '/u01/oradata/orcl/demo01.dbf';
    
    Starting backup at 06/11/2008 20:40:28
    allocated channel: ORA_DISK_1
    channel ORA_DISK_1: sid=142 devtype=DISK
    channel ORA_DISK_1: starting datafile copy
    input datafile fno=00006 name=/u01/oradata/orcl/�¡Ã³demo01.dbf
    output filename=/u01/oradata/orcl/demo01.dbf tag=TAG20081106T204029 recid=4 stamp=670106430
    channel ORA_DISK_1: datafile copy complete, elapsed time: 00:00:01
    Finished backup at 06/11/2008 20:40:30
    
  4. Ahora intercambiamos los archivos.
    RMAN> switch datafile 6 to copy;
    
    datafile 6 switched to datafile copy "/u01/oradata/orcl/demo01.dbf"
    
  5. Finalizamos poniendo el tablespace en linea.
    RMAN> sql "alter tablespace demo online";
    
    sql statement: alter tablespace demo online
    
  6. Verificamos.
    RMAN> report schema;
    
    using target database control file instead of recovery catalog
    Report of database schema
    
    List of Permanent Datafiles
    ===========================
    File Size(MB) Tablespace       RB segs Datafile Name
    ---- -------- ---------------- ------- ------------------------
    1    480      SYSTEM           ***     /u01/oradata/orcl/system01.dbf
    2    30       UNDOTBS1         ***     /u01/oradata/orcl/undotbs01.dbf
    3    230      SYSAUX           ***     /u01/oradata/orcl/sysaux01.dbf
    4    5        USERS            ***     /u01/oradata/orcl/users01.dbf
    5    100      EXAMPLE          ***     /u01/oradata/orcl/example01.dbf
    6    10       DEMO             ***     /u01/oradata/orcl/demo01.dbf
    List of Temporary Files
    =======================
    File Size(MB) Tablespace           Maxsize(MB) Tempfile Name
    ---- -------- -------------------- ----------- --------------------
    1    20       TEMP                 32767       /u01/oradata/orcl/temp01.dbf

El problema ha sido resuelto!

Puedes revisar el Note:743616.1 Rename Datafiles Using Rman, para ver otros escenarios posibles.

Posts Relacionados:
Siga leyendo >>

¿Te pareció interesante este artículo?, ¿te quedaron algunas dudas?, ¿quieres sugerirme un tema a tratar?, pues déjame tus comentarios o envíame un email y para que NO te pierdas ningún Post, suscríbete por email ahora mismo!

sábado, 1 de noviembre de 2008

Cursor sharing, histograms y bind peeking, lo que deberías saber para evitar sorpresas.

"Si fuese a escribir un libro sobre cómo construir aplicaciones Oracle no escalables, entonces "No Use Bind Variables" sería el título del primer y último capítulo." (Thomas Kyte)
No es infrecuente toparnos con aplicaciones que hacen caso omiso del principio de usar bind variables y vemos como el desempeño se va al suelo por la elevada tasa de hard parsing que se introduce. Casi sin pensarlo nos refugiamos en el uso del parámetro cursor_sharing como nuestra única alternativa para capear el temporal, pero ¿sabías que bajo determinadas circunstancias lo único que se logra es exacerbar el problema?

Para poder comprenderlo mejor, empecemos por recordar algo de teoría sobre el parámetro cursor_sharing. De acuerdo a la documentación podemos asignarle uno de tres valores:
  • Force. Fuerza a las sentencias que pueden diferir en algunos literales, pero que de otra forma son idénticas, a compartir un mismo cursor.
  • Similar. Causa que las sentencias que difieren en algunos literales, pero que de otra forma son idénticas, compartan un mismo cursor, a menos que los literales afecten ya sea el significado de la sentencia o el grado al cual el plan de ejecución es optimizado.
  • Exact (default). Sólo permite que sentencias con un texto idéntico compartan un mismo cursor.

Ahora, para ir viendo el efecto de ir modificando este parámetro, consideraremos una aplicación hipotética que genera sentencias de este tipo:

select avg(sal) from emp where deptno = 10
select avg(sal) from emp where deptno = 20
select avg(sal) from emp where deptno = 30
select avg(sal) from emp where deptno = 40
select avg(sal) from emp where deptno = 50

Asumiremos también que la tabla emp cuenta con un índice en la columna deptno y que hay una distribución no uniforme de sus contenidos:

SCOTT@orcl > select deptno, count(*)
2  from emp group by deptno;

DEPTNO   COUNT(*)
---------- ----------
10     200000
20          1
30          1
40          1
50          1

Finalmente nos aseguramos de contar con estadísticas actualizadas:

SCOTT@orcl > exec dbms_stats.gather_table_stats( ownname=>'SCOTT', tabname=>'EMP', cascade=>TRUE, method_opt => 'for all indexed columns size auto' )

Teniendo en cuenta que por defecto cursor_sharing=exact, al inspeccionar el Shared Pool no es sorpresa encontrar que cada sentencia de nuestra aplicación hipotética genere su propio cursor:

SYS@orcl > select sql_text, executions, child_number, plan_hash_value
2  from v$sql where sql_text like 'select max(sal)%';

SQL_TEXT                  CHILD_NUMBER EXECUTIONS PLAN_HASH_VALUE
------------------------- ------------ ---------- ---------------
select max(sal) from emp             0          1      2233664114
where deptno = 40

select max(sal) from emp             0          1      2083865914
where deptno = 10

select max(sal) from emp             0          1      2233664114
where deptno = 20

select max(sal) from emp             0          1      2233664114
where deptno = 50

select max(sal) from emp             0          1      2233664114
where deptno = 30

Si observamos con cuidado, notaremos que al consultar por deptno=10, el plan de ejecución es distinto que para los demás casos. Esto se explica por el hecho de que el histograma existente permite al optimizador deducir que un full table scan es lo óptimo, para los demás casos escoge más bien el uso del índice existente por la columna dept. Esta hipótesis es corroborada inspeccionando los planes de ejecución:

Plan hash value: 2083865914

----------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)|
----------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |       |       |   283 (100)|
|   1 |  SORT AGGREGATE    |      |     1 |    16 |            |
|*  2 |   TABLE ACCESS FULL| EMP  |   202K|  3162K|   283   (4)|
----------------------------------------------------------------

Plan hash value: 2233664114

-----------------------------------------------------------------------------
| Id  | Operation                    | Name    | Rows  | Bytes | Cost (%CPU)|
-----------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |         |       |       |     4 (100)|
|   1 |  SORT AGGREGATE              |         |     1 |    16 |            |
|   2 |   TABLE ACCESS BY INDEX ROWID| EMP     |     1 |    16 |     4   (0)|
|*  3 |    INDEX RANGE SCAN          | EMP_DEP |     1 |       |     3   (0)|
-----------------------------------------------------------------------------

Ha llegado el momento de hacer modificaciones, el primer cambio a experimentar será cursor_sharing=force, ejecutamos los queries y volvemos a inspeccionar el Shared Pool:

SQL_TEXT                  CHILD_NUMBER EXECUTIONS PLAN_HASH_VALUE
------------------------- ------------ ---------- ---------------
select max(sal) from emp             0          5      2083865914
where deptno = :"SYS_B_0"

Lo primero que notamos es que ahora solamente se emplea un cursor, lo cual nos alienta a creer que hemos mejorado la situación, pero luego nos percatamos que el plan de ejecución corresponde al del Full Table Scan, es decir hemos trasladado el problema desde el Shared Pool hacia el Buffer Cache, aumentando la cantidad de lecturas físicas y lógicas. ¿A qué se debe esto? Ha llegado la hora de hablar de Bind Peeking.

Bind Peeking fue introducido en Oracle 9i y consiste en que el optimizador le da un vistazo a los valores de las bind variables en la primera invocación del cursor, esto le permite determinar la selectividad y escoger un plan adecuado, a partir de allí toda invocación de este cursor ya no pasa por esta inspección y el plan de ejecución es automáticamente aplicado.

Alteremos ahora el orden de los queries para corroborar la teoría, dejaremos la consulta por deptno=10 para el final y miren lo que encontramos en el Shared Pool:

SQL_TEXT                  CHILD_NUMBER EXECUTIONS PLAN_HASH_VALUE
------------------------- ------------ ---------- ---------------
select max(sal) from emp             0          5      2233664114
where deptno = :"SYS_B_0"

Todas los queries comparten el mismo cursor, pero ahora con el plan de ejecución que usa el índice por deptno.

Llegamos a la conclusión que usar cursor_sharing=force es como jugar a la ruleta rusa, dependiendo qué sentencia llegue primero al Shared Pool su plan de ejecución será el que prevalezca con resultados nada convenientes. Pero ¿y si usamos cursor_sharing=similar? ¿Será la solución mágica que estamos esperando?, pues ejecutemos nuevamente los queries e inspeccionemos el Shared Pool:

SQL_TEXT                  CHILD_NUMBER EXECUTIONS PLAN_HASH_VALUE
------------------------- ------------ ---------- ---------------
select max(sal) from emp             0          1      2083865914
where deptno = :"SYS_B_0"

select max(sal) from emp             1          1      2233664114
where deptno = :"SYS_B_0"

select max(sal) from emp             2          1      2233664114
where deptno = :"SYS_B_0"

select max(sal) from emp             3          1      2233664114
where deptno = :"SYS_B_0"

select max(sal) from emp             4          1      2233664114
where deptno = :"SYS_B_0"

Se ha creado un cursor por cada sentencia, igual que cuando cursor_sharing=exact, pero los textos son iguales y se ve que tienen bind variables, ¿qué pasó entonces? Pues con cursor_sharing=similar los cursores son compartidos siempre y cuando el optimizador considere seguro hacerlo así. Para nuestro escenario, el optimizador considera inseguro compartir los cursores debido a la existencia de un histograma para la columna deptno, para probarlo eliminamos el histograma, ejecutamos las sentencias e inspeccionamos el Shared Pool:

SCOTT@orcl > exec dbms_stats.gather_table_stats( ownname=>'SCOTT', tabname=>'EMP', cascade=>FALSE, method_opt => 'for columns deptno size 1' )

SQL_TEXT                  CHILD_NUMBER EXECUTIONS PLAN_HASH_VALUE
------------------------- ------------ ---------- ---------------
select max(sal) from emp             0          5      2083865914
where deptno = :"SYS_B_0"

Todas las sentencias comparten el mismo cursor, pero al carecer de histogramas el Optimizador no es capaz de identificar que algunas sentencias se benefician del uso del índice por deptno, y termina escogiendo Full Table Scan (FTS) al creer que hay un único valor:

SCOTT@orcl > select num_distinct, histogram from user_tab_cols
2  where table_name = 'EMP' and column_name = 'DEPTNO';

NUM_DISTINCT HISTOGRAM
------------ ---------------------------------------------
1 NONE

Si ahora borramos totalmente las estadísticas de la columna deptno, obtenemos otro resultado:

SCOTT@orcl > exec dbms_stats.delete_column_stats( ownname => 'SCOTT', tabname => 'EMP', colname=>'DEPTNO')

SQL_TEXT                  CHILD_NUMBER EXECUTIONS PLAN_HASH_VALUE
------------------------- ------------ ---------- ---------------
select max(sal) from emp             0          5      2233664114
where deptno = :"SYS_B_0"

Ahora el Optimizador opta por usar el índice por deptno, explicado por el hecho de que ahora no cuenta con información y asume que la selectividad es alta:

SCOTT@orcl > select num_distinct, histogram from user_tab_cols
2  where table_name = 'EMP' and column_name = 'DEPTNO';

NUM_DISTINCT HISTOGRAM
------------ ---------------------------------------------
NONE

Resumiendo, con cursor_sharing=similar en caso de existir histogramas los cursores no serán compartidos y al no poder recurrir a información sobre la distribución de los datos, el Optimizador puede escoger un plan poco eficiente.

Existe un detalle final que no podemos pasar por alto y es que por defecto Oracle genera las estadísticas con histogramas, lo cual ciertamente afecta el posible efecto benéfico de emplear cursor_sharing=similar.

SYS@orcl > select dbms_stats.get_param('method_opt') from dual;
DBMS_STATS.GET_PARAM('METHOD_OPT')
----------------------------------
FOR ALL COLUMNS SIZE AUTO

En conclusión, nada es mejor que desarrollar las aplicaciones haciendo uso debido de bind variables, tratar de corregir el error inicial de no usarlos resulta en una tarea larga y costosa, si creemos que es facilmente superable con el parámetro cursor_sharing, pues nos equivocamos, en el mejor de los casos es únicamente un paliativo, una aspirina con la que pretendemos dar tratamiento a un cáncer, así que a hacer un mejor seguimiento a los equipos de desarrollo de aplicaciones, y si estamos por adquirir una aplicacion empaquetada, a revisar el código fuente, no vaya a ser que terminemos adquiriendo un problema en lugar de una solución.

Si deseas profundizar en el tema, revisa los Notes: 377847.1 Unsafe Peeked Bind Variables and Histograms, 261020.1 High Version Count with CURSOR_SHARING = SIMILAR or FORCE, 296377.1 Troubleshooting Guide to high version_counts, 430208.1 Bind Peeking By Example y 369427.1 Case Study: The Mysterious Performance Drop.

Posts Relacionados:
Siga leyendo >>

¿Te pareció interesante este artículo?, ¿te quedaron algunas dudas?, ¿quieres sugerirme un tema a tratar?, pues déjame tus comentarios o envíame un email y para que NO te pierdas ningún Post, suscríbete por email ahora mismo!

¿Cómo ocultar código fuente PL/SQL? Use Wrap.

Imaginemos un escenario en el cual hemos desarrollado un procedimiento almacenado en PL/SQL y deseamos evitar que alguien, incluido el DBA, pueda ver y/o modificar el código fuente. ¿Es eso posible?, definitivamente sí: Oracle provee el utilitario Wrap para lograrlo, pero implicaba invocarlo desde el Sistema Operativo; a partir de 10g es posible invocarlo desde dentro de la base de datos. Veamos cómo hacer uso de ambas modalidades.

Primero un vistazo al código que me interesa ocultar:
[oracle@caliope ~]$ cat salary.sql
CREATE OR REPLACE
FUNCTION salary (
empno emp.empno%TYPE )
RETURN NUMBER
IS
sal emp.sal%TYPE;
BEGIN
SELECT sal INTO salary.sal
FROM emp
WHERE empno = salary.empno;
RETURN ( salary.sal );
END;

La primera alternativa es usar el utilitario Wrap desde el Sistema Operativo.
  1. Hay que invocarlo referenciando al archivo que contiene el código fuente.
  2. [oracle@caliope ~]$ wrap iname=salary.sql
    
    PL/SQL Wrapper: Release 10.2.0.4.0- Production on Sat Nov 01 14:21:47 2008
    
    Copyright (c) 1993, 2004, Oracle.  All rights reserved.
    
    Processing salary.sql to salary.plb
  3. Verificamos el resultado.
  4. [oracle@caliope ~]$ cat salary.plb
    CREATE OR REPLACE
    FUNCTION salary wrapped
    a000000
    367
    abcd
    abcd
    abcd
    abcd
    abcd
    abcd
    abcd
    abcd
    abcd
    abcd
    abcd
    abcd
    abcd
    abcd
    abcd
    8
    cc d6
    +/IgG5qcUWhn5W4aX1Z1gJbmalUwgy5KLcvWfHSi2sHVSCY2DKrK4KYn8hQGyrdN4JG7orD5
    ELaEY9EXT7sxm8hT0NF8GQX2BSRNdGc/i2nS6FUq4oJr3+Z/vda3u1pli6dW3SXcl4vlGtfr
    ShxK0Ikb/IzmSgsWLtX2Nyo5Ob28iiwzrfFQOmdO1j2jRP8s5Z3aTTuovLrHOuGA3Hyl
    
    /
  5. Introducimos el código en la base de datos.
  6. [oracle@caliope ~]$ sqlplus scott/tiger @salary.plb
    
    SQL*Plus: Release 10.2.0.4.0 - Production on Sat Nov 1 14:23:59 2008
    
    Copyright (c) 1982, 2007, Oracle.  All Rights Reserved.
    
    
    Connected to:
    Oracle Database 10g Enterprise Edition Release 10.2.0.4.0 - Production
    With the Partitioning, OLAP, Data Mining and Real Application Testing options
    
    
    Function created.
  7. Comprobamos que la funcion está operativa.
  8. SCOTT@orcl > select salary( 7654 ) from dual;
    
    SALARY(7654)
    ------------
    1312.5
    
    SCOTT@orcl >

Como resultado tenemos ahora un procedimiento almacenado cuyo código fuente es ininteligible a simple vista.

¿Qué hacer si el código ya está en la base de datos? Pues podemos extraerlo a un archivo y seguir el procedimiento previo, pero tambien es posible hacer uso del mejorado package DBMS_DDL, entonces manos a la obra.
  1. Usaremos la siguiente rutina para transformar el código fuente.
  2. [oracle@caliope ~]$ cat wrapped.sql
    DECLARE
    v_source DBMS_SQL.VARCHAR2A;
    BEGIN
    v_source(1) := 'CREATE OR REPLACE ';
    FOR s IN ( SELECT line, text
    FROM user_source
    WHERE name = 'SALARY'
    ORDER BY line )
    LOOP
    v_source(s.line+1) := s.text;
    END LOOP;
    dbms_ddl.create_wrapped(
    ddl => v_source,
    lb => 1,
    ub => v_source.count );
    END;
  3. Ahora lo ejecutamos.
  4. SCOTT@orcl > @wrapped
    
    PL/SQL procedure successfully completed.
  5. Verificamos que el código fuente fue modificado.
  6. SCOTT@orcl > select text from user_source
    2  where name = 'SALARY' order by line;
    
    TEXT
    --------------------------------------------------------------------------------
    FUNCTION salary wrapped
    a000000
    369
    abcd
    abcd
    abcd
    abcd
    abcd
    abcd
    abcd
    abcd
    abcd
    abcd
    abcd
    abcd
    abcd
    abcd
    abcd
    8
    c3 d2
    NL5qz7Eyn6/UUHrpUacIYXrnSkQwgy7wLcsVfHSiWHOUxMoNmSQvf6CVAlrwXr70aca5WXwI
    /nAKWlYzkLkxeCuI8kessTO0QSAhNtwDkRNJ6sbC/1+niQxcN3d3A9MG5qanSMCbmJCZRkFk
    zHqG9AcGWonxLvVFqyc+fCfcn4q5PwtD7ZLkhFU52f6bi7u9g2dWnLjOpqUrlos=
  7. Finalmente veamos si está operativo.
  8. SCOTT@orcl > select salary( 7654 ) from dual;
    
    SALARY(7654)
    ------------
    1312.5

Nuevamente el objetivo se ha cumplido!

En conclusión, hemos visto dos formas de transformar nuestro código fuente en un texto indescifrable apoyados de Wrap. El código mostrado no está libre de errores ni está optimizado, pero les puede servir como una base. Ah!, y no olviden darle una ojeada a la documentación, para que tengan un panorama completo de las posibilidades existentes.

Posts Relacionados:
Siga leyendo >>

¿Te pareció interesante este artículo?, ¿te quedaron algunas dudas?, ¿quieres sugerirme un tema a tratar?, pues déjame tus comentarios o envíame un email y para que NO te pierdas ningún Post, suscríbete por email ahora mismo!