martes, 22 de julio de 2008

Character Set en SqlPlus de Windows en Español

Hasta que no empecé este blog, nunca me había preocupado por tener el characterset correcto en mi sesión de sqlplus en DOS.


SQL> commit;

Confirmaci¾n terminada.


El caracter ¾ debería en realidad ser "ó". lo primero que intenté fue cambiar el characterset a lo que generalmente pudiera usar en un sistema linux.


C:\oracle\product\10.2.0\BIN>set NLS_LANG=SPANISH_AMERICA.WE8ISO8859P1

SQL> commit;

Confirmaci¾n terminada.

C:\oracle\product\10.2.0\BIN>set NLS_LANG=SPANISH_AMERICA.UTF8

SQL> commit;

Confirmaci├│n terminada.


Como nada de esto funcionó, lo primero fue averiguar lo que realmente estaba usando como characterset:


C:\oracle\product\10.2.0\database>echo %NLS_LANG%
%NLS_LANG%

SQL> @.[%NLS_LANG%].
SP2-0310: no se ha podido abrir el archivo ".[SPANISH_SPAIN.WE8MSWIN1252]..sql"


La línea de comando en windows (DOS) no usa los códigos de página Western European, por lo cual debemos de obtener el código que se usa realmente.

Para eso windows tiene la utilería chcp


C:\oracle\product\10.2.0\BIN>chcp
Tabla de códigos activa: 850



Con esta información, podemos buscar el characterset correcto en la siguiente lista:


MS-DOS code page Oracle Client character set (3rd part of NLS_LANG)
437 US8PC437
737 EL8PC737
850 WE8PC850
852 EE8PC852
857 TR8PC857
858 WE8PC858
861 IS8PC861
862 IW8PC1507
865 N8PC865
866 RU8PC866

C:\oracle\product\10.2.0\BIN>set NLS_LANG=SPANISH_AMERICA.WE8PC850

SQL> commit;

Confirmación terminada.

viernes, 18 de julio de 2008

Group by VS. Distinct

Mucho se habla sobre la diferencia entre group by y Distinct, la verdad es que en oracle parece no tener diferencia.

Hice algunas pruebas para poder decir que son prácticamente lo mismo.

Empecé con los siguientes queries:


SQL> explain plan for
2 SELECT DISTINCT campo1 FROM prueba;

Explicado.

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------

Plan hash value: 643035693

-----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 5 | 4 (25)| 00:00:01 |
| 1 | HASH UNIQUE | | 1 | 5 | 4 (25)| 00:00:01 |
| 2 | TABLE ACCESS FULL| PRUEBA | 1 | 5 | 3 (0)| 00:00:01 |
-----------------------------------------------------------------------------


SQL> explain plan for
2 SELECT campo1 FROM prueba GROUP BY campo1;

Explicado.

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------

Plan hash value: 287650557

-----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 5 | 4 (25)| 00:00:01 |
| 1 | HASH GROUP BY | | 1 | 5 | 4 (25)| 00:00:01 |
| 2 | TABLE ACCESS FULL| PRUEBA | 1 | 5 | 3 (0)| 00:00:01 |
-----------------------------------------------------------------------------


Como se puede ver lo único que cambia es el "HASH UNIQUE" por "HASH GROUP", el resto del plan parece ser igual.

Al ser una función hash, decidí incrementar la prueba a un set de datos más grande (suficiente para que mi PGA se quedara corta y se tuviera que pasar la tabla de hash a disco). Decidí también poner un trace nivel 10104 al proceso para ver la creación de la tabla de hash.

Las sentencias SQL son las siguientes:


WITH registros AS
(SELECT /*+MATERIALIZE*/
owner,
object_type
FROM dba_objects)
SELECT COUNT(*)
FROM
(SELECT owner,
object_type || rownum
FROM
(SELECT a.owner,
b.object_type
FROM registros a,
registros b
WHERE rownum < 1000000)
GROUP BY owner,
object_type || rownum);


WITH registros AS
(SELECT /*+MATERIALIZE*/
owner,
object_type
FROM dba_objects)
SELECT COUNT(*)
FROM
(SELECT DISTINCT owner,
object_type || rownum
FROM
(SELECT a.owner,
b.object_type
FROM registros a,
registros b
WHERE rownum < 1000000))
;



Las pruebas que realicé fueron las siguientes:



SQL> oradebug setmypid
Sentencia procesada.
SQL> oradebug event 10104 trace name context forever, level 12;
Sentencia procesada.
SQL> WITH registros AS
2 (SELECT /*+MATERIALIZE*/
...
15 WHERE rownum < 1000000));

COUNT(*)
----------
999999

SQL> oradebug tracefile_name
c:\oracle\product\admin\orcl\udump\orcl_ora_5272.trc


En ambas situaciones, las tablas de hash fueron exactamente las mismas...



*** RowSrcId: 6 HASH JOIN BUILD HASH TABLE (PHASE 1) ***
Total number of partitions: 8
Number of partitions which could fit in memory: 8
Number of partitions left in memory: 8
Total number of slots in in-memory partitions: 8
Total number of rows in in-memory partitions: 69
(used as preliminary number of buckets in hash table)
Estimated max # of build rows that can fit in avail memory: 81720
*** (continued) HASH JOIN BUILD HASH TABLE (PHASE 1) ***
Requested size of hash table: 16
Actual size of hash table: 16
Number of buckets: 128
Match bit vector allocated: FALSE
kxhfResize(enter): resize to 14 slots (numAlloc=8, max=12)
kxhfResize(exit): resized to 14 slots (numAlloc=8, max=14)
freeze work area size to: 2321K (14 slots)
### Hash table overall statistics ###
Total buckets: 128 Empty buckets: 73 Non-empty buckets: 55
Total number of rows: 69
Maximum number of rows in a bucket: 3
Average number of rows in non-empty buckets: 1.254545



En ambas ocasiones, la tabla de hash fue exactamente la misma, con el mismo número de operaciones. A nivel SQL trace, las ejecuciones también fueron similares:


Group By

call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 1 0.31 0.38 0 5395 143 0
Fetch 2 2.89 4.07 2615 144 1 1
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 4 3.20 4.45 2615 5539 144 1

Distinct

call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 1 0.28 0.33 0 5395 143 0
Fetch 2 2.92 4.01 2615 144 1 1
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 4 3.20 4.34 2615 5539 144 1



Intenté hacer algunas pruebas para ver si alguno era más eficiente que otros en Joins y no encontré ninguna diferencia.

Después de muchas pruebas sólo encontré una diferencia...


SQL> select sql_text,
2 sharable_mem
3 FROM v$sql
4 WHERE sql_text LIKE 'SELECT%campo1%prueba%';

SQL_TEXT SHARABLE_MEM
----------------------------------------- ------------
SELECT DISTINCT campo1 FROM prueba 8535
SELECT campo1 FROM prueba GROUP BY campo1 8542


La sentencia sql ocupa más caracteres en el group by, por lo mismo ocupa más memoria dentro del shared_pool.

Ya que esto es insignificante, se puede decir que el "group by" y el "disticnt" son similares.

domingo, 13 de julio de 2008

Mejora de desempeño con Oracle Text

Existen muchos casos de sentencias SQL que lo que intentan a partir de varios campos, saber qué registros cumplen con una palabra "clave". Imaginemos lo siguiente, llega una persona a generar una factura en un grupo que tiene 1 millón de clientes, y el dato que podemos dar para buscar el cliente a nombre de quién se factura es el nombre "Hugo", pero la definición de la tabla tiene nombre, segundo_nombre, primer_apellido, segundo_apellido y razon_social.

Imaginemos también que no se tiene ningún tipo de constraint para evitar el uso indistinto de Mayúsculas/Minúsculas.

Así que aquí tenemos un ejemplo:


SQL> create table catalogo
2 (nombre varchar2(30),
3 segundo_nombre varchar2(30),
4 primer_apellido varchar2(30),
5 segundo_apellido varchar2(30),
6 razon_social varchar2(100));

Tabla creada.

SQL> insert into CATALOGO
2 with registros as (
3 select OWNER, OBJECT_NAME, OBJECT_TYPE
4 from dba_objects
5 )
6 select
7 t2.owner,
8 substr(t2.OBJECT_NAME,mod(rownum,10),5),
9 substr(t2.OBJECT_TYPE,1,mod(rownum,10)),
10 substr(t2.OBJECT_NAME,1,mod(rownum,10)),
11 t2.owner||' '||t2.object_type||' '||t2.object_name
12 from
13 registros t1,
14 registros t2
15 where rownum < 1000000;

999999 filas creadas.



SQL> insert into catalogo values('Hugo','Enrique','Contreras','Gamiño',null);

1 fila creada.

SQL> commit;


Ahora ya tenemos 1 millón de registros, y podemos hacer uso de nuestra palabra clave "ENRIQUE" que sabemos de antemano que sólo nos regresará un registro.

SQL> set timing on
SQL> set autot traceonly stat
SQL> select * from catalogo
2 where upper(nombre||segundo_nombre||primer_apellido||segundo_apellido||razon_social)
3 like '%ENRIQUE%';

Transcurrido: 00:00:14.67

EstadÝsticas
----------------------------------------------------------
285 recursive calls
0 db block gets
10087 consistent gets
10031 physical reads
116 redo size
712 bytes sent via SQL*Net to client
396 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
2 sorts (memory)
0 sorts (disk)
1 rows processed
>

Cómo se puede ver, hay varias situaciones aquí, la primera es que el desarrollador tuvo que concatenar todos los campos descriptivos y además se aplicó la función "UPPER". La segunda es que no importa qué índice de tipo B-TREE se cree, se terminará haciendo un Full Table Scan o Full Index Scan.

La solución qu eparece ser más óptima es con el uso de Oracle Text.

Se eligió para este ejemplo un índice de tipo "Context". Como primer paso se debe de crear un datastore de multicolumna que englobe los campos descriptivos de la tabla que queremos indexar.


SQL> begin
2 ctx_ddl.create_preference('mi_datastore', 'multi_column_datastore');
3 ctx_ddl.set_attribute('mi_datastore', 'columns',
4 'nombre,segundo_nombre,primer_apellido,segundo_apellido,razon_social');
5 end;
6 /

Procedimiento PL/SQL terminado correctamente.



Una vez creado el datastore, se puede crear el índice.


SQL> create index cat_texto_idx on catalogo(nombre)
2 indextype is ctxsys.context
3 parameters('datastore mi_datastore');

Indice creado.


El índice es creado sobre la columns "nombre" y cada registro es tomado encuenta como un documento, por lo cual, oracle text nos permite buscar por el campo nombre y hacer referencia a el datastore múltiple.


SQL> select * from catalogo
2 where contains(nombre,'enrique',1)>0;

Transcurrido: 00:00:00.02

EstadÝsticas
----------------------------------------------------------
11 recursive calls
0 db block gets
21 consistent gets
0 physical reads
0 redo size
712 bytes sent via SQL*Net to client
396 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
1 rows processed


En este ejemplo observamos que se usa la función "Contains" de oracle text para buscar sobre un campo indexado (que en nuestro ejemplo es nombre). Se puede observar también que para este ejemplo, es indistinto el uso de mayúsculas o minúsculas. y que en lugar de leer más de 10,000 bloques de datos, sólo se leen 21 bloques. en lugar de tardar más de 10 segundos, sólo se tardó el query 0.020 segundos.

Hay algo que se debe de tomar en cuenta, un índice de contexto no es transaccional por default, es decir, a medida que los datos se van modificando (Cualquier DML) el índice queda fuera de sincronía.


SQL> insert into catalogo values('Diego','Armando','Maradona',null,null);

1 fila creada.

SQL> commit;

Confirmaci¾n terminada.

SQL> select * from catalogo
2 where contains(nombre,'maradona',1)>0;

ninguna fila seleccionada.



Para mantener la sincronía, existe el siguiente comando


SQL> begin
2 CTX_DDL.SYNC_INDEX('CAT_TEXTO_IDX','50K');
3 end;
4 /

Procedimiento PL/SQL terminado correctamente.

SQL> select * from catalogo
2 where contains(nombre,'maradona',1)>0;

NOMBRE SEGUNDO_NO PRIMER_APE SEGUNDO_AP RAZON_SOCI
---------- ---------- ---------- ---------- ----------
Diego Armando Maradona



Ya que esto puede ser inaceptable para muchos modelos de sistemas, oracle tiene una propiedad (que no es default) para poder sincronizar los índices.


SQL> drop index cat_texto_idx;

Indice borrado.

SQL> create index cat_texto_idx on catalogo(nombre)
2 indextype is ctxsys.context
3 parameters('datastore mi_datastore sync (on commit)');

Indice creado.

SQL> insert into catalogo values('Eric','Daniel','Cantona',null,null);

1 fila creada.

SQL> select * from catalogo where contains(nombre,'cantona',1)>0;

ninguna fila seleccionada

SQL> commit;

Confirmaci¾n terminada.

SQL> select * from catalogo where contains(nombre,'cantona',1)>0;

NOMBRE SEGUNDO_NO PRIMER_APE SEGUNDO_AP RAZON_SOCI
---------- ---------- ---------- ---------- ----------
Eric Daniel Cantona


El índice quedará sincronizado cada que nosotros hagamos commit;

Todo esto es sólo un ejemplo práctico que me ayudó a resolver un problema de desempeño en un sistema, sin embargo es muy limitado en cuanto al uso de Oracle text, por eso recomiendo leer el Manual de referencia de oracle text.

viernes, 11 de julio de 2008

Recuperar un datafile borrado accidentalmente sin respaldo

Recientemente me encontré con un problema en producción donde una base de datos misteriosamente había perdido un datafile recientemente agregado. Pareciera como si le hubieran dado un "rm /data/file.dbf" lo cual parece ser el problema, ya que la referencia al datafile quería ser eliminada. Vale la pena decir que en Locally Managed Tablespaces sólo se pueden eliminar datafiles que están online, por esta razón se debía de recuperar el datafile perdido para poder eliminarlo.

La base de datos en cuestión estaba en estado de abierta, pero el tablespace al que pertenecía este datafile se quedó offline y no se podía poner online.

Hay muchas formas de sobrepasar este problema, si el tablespace hubiera sido de índices, se podrían haber recreado todos los índices en un nuevo tablespace y simplemente haber realizado un drop del tablespace.

En este caso, lo que se realizo fue recrear el datafile nuevamente y recupear para poner online el tablespace y poder hacer el drop del datafile.

Voy a ejemplificar el problema en windows (que por la naturaleza del sistema operativo, no se puede borrar accidentalmente el datafile).


SQL> create tablespace prueba
2> datafile 'c:\prueba01.dbf' size 10m,
3> 'c:\prueba02.dbf' size 10m;

Tablespace creado.

SQL> shutdown ;
Base de datos cerrada.
Base de datos desmontada.
Instancia ORACLE cerrada.

SQL> $ del c:\prueba02.dbf

SQL> startup
Instancia ORACLE iniciada.

Total System Global Area 289406976 bytes
Fixed Size 1290184 bytes
Variable Size 264241208 bytes
Database Buffers 16777216 bytes
Redo Buffers 7098368 bytes
Base de datos montada.
ORA-01157: no se puede identificar/bloquear el archivo de datos 8 - consulte el archivo de rastreo del DBWR
ORA-01110: archivo de datos 8: 'C:\PRUEBA02.DBF'

SQL> select * from v$recover_file;

FILE# ONLINE ONLINE_ ERROR
---------- ------- ------- --------------
8 ONLINE ONLINE FILE NOT FOUND


Como se puede ver, la base de datos no encuentra el datafile que hemos borrado. No se tiene un respaldo del mismo, pero hay una forma de recuperarlo.


SQL> alter database create datafile 8 as 'C:\PRUEBA02.DBF' size 10m;

Base de datos modificada.

SQL> recover datafile 8;
Recuperaci¾n del medio fÝsico terminada.
SQL> alter database open;

Base de datos modificada.


Se puede recrear usando "alter database create datafile 8 as..." o bien "alter database create datafile 'c:\PRUEBA02.DBF' as...".

De esta forma recreamos el datafile al origen del mismo, y al recuperar el datafile aplicamos los cambios que la base tiene registrados para el mismo.

La recuperación del datafile se puede realizar con rman:


C:\Documents and Settings>rman target /

Recovery Manager : Release 10.2.0.3.0

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

conectado a la base de datos destino: ORCL (DBID=524232147, no abierto)

RMAN> restore datafile 8;

Iniciando restore en 11/07/08
se utiliza el archivo de control de la base de datos destino en lugar del catßlogo de recuperaci¾n
canal asignado: ORA_DISK_1
canal ORA_DISK_1: sid=155 devtype=DISK

creando archivo de datos fno=8 nombre=C:\PRUEBA02.DBF
no se ha realizado la restauraci¾n; todos los archivos son de s¾lo lectura, offline o ya se han restaur
restore terminado en 11/07/08

RMAN> recover datafile 8;

Iniciando recover en 11/07/08
se utiliza el archivo de control de la base de datos destino en lugar del catßlogo de recuperaci¾n
canal asignado: ORA_DISK_1
canal ORA_DISK_1: sid=159 devtype=DISK

iniciando la recuperaci¾n del medio fÝsico
recuperaci¾n del medio fÝsico terminada, tiempo transcurrido: 00:00:02

recover terminado en 11/07/08

RMAN> exit

C:\Documents and Settings>sqlplus "/ as sysdba"

SQL*Plus: Release 10.2.0.3.0

Copyright (c) 1982, 2006, Oracle. All Rights Reserved.


Conectado a:
Oracle Database 10g Enterprise Edition Release 10.2.0.3.0 - Production
With the Partitioning, OLAP and Data Mining options

SQL> alter database open;

Sistema modificado.



Una vez recuperado el datafile, se puede eliminar de la siguiente forma:


SQL> alter tablespace prueba drop datafile 8;

Tablespace modificado.

viernes, 27 de junio de 2008

Tipos de Datos Fecha



muchas veces me he encontrado con problemas relacionados a la búsqueda de información basada en fechas.

El siguiente tipo de query es muy común:


SQL> select count(1)
2 from ra_customer_trx_all
3 where trunc(creation_date) between
4 to_date('01/04/2008','dd/mm/yyyy')
5 and to_date('30/04/2008','dd/mm/yyyy');

COUNT(1)
----------
206171


Y siempre encuentro el mismo razonamiento de los desarrolladores. "Debido a que la fehca viene guardada con un timestamp, es necesario poner un trunc()".

Y mi respuesta siempre es la siguiente:

Oracle no es que guarde una fecha con o sin formato. Oracle guarda una fecha! Creo que el paradigma de que oracle guarda un formato es erroneo, Oracle solo guarda un tipo de dato fecha, y las sesiones son las que llevan un formato. por esa rozón siempre recomiendo cambiar el tipo de query de arriba por el siguiente:


SQL> select count(1)
2 from ra_customer_trx_all
3 where creation_date >= to_date('01/04/2008','dd/mm/yyyy')
4* and creation_date < to_date('30/04/2008','dd/mm/yyyy') + 1

COUNT(1)
----------
206171

El resultado es el mismo, y en caso de tener un índice creado en el campo creation_date seguramente será usado, de forma contraria al trunc(), ya que al aplicar una función al campo, se evita el uso del índice.

Para poder revisar cómo se guarda un tipo de dato fecha en oracle, hacemos lo siguiente:


SQL> create table prueba (campo1 char(3),campo2 date, campo3 char(3));

Tabla creada.

SQL> insert into prueba values('XXX',to_date('01/01/2000','dd/mm/yyyy'),'YYY');

1 fila creada.

SQL> commit;

Confirmaci¾n terminada.

SQL> SELECT dbms_rowid.rowid_relative_fno(rowid) "Datafile",
2 dbms_rowid.rowid_block_number(rowid) "Bloque"
3 FROM prueba;

Datafile Bloque
---------- ----------
1 63314

SQL> alter system dump datafile 1 block 63314;

Sistema modificado.

SQL> oradebug setmypid
Sentencia procesada.
SQL> oradebug tracefile_name;
c:\oracle\product\admin\orcl\udump\orcl_ora_3196.trc


Una vez que hacemos el dump del bloque y sabiendo que el heade del bloque crece de arriba hacia abajo y los datos del bloque de abajo hacia arriba, nos vamos al final del bloque


ABCC7D0 19330E1E 066C7807 353B1303 30303213 [..3..xl...;5.200]
ABCC7E0 38302D35 3A30332D 03012C31 58585803 [5-08-30:1,...XXX]
ABCC7F0 01647807 01010101 59595903 68820601 [.xd......YYY...h]
Block header dump: 0x0040f752


Vemos que se repiten 3 veces el 0x58 y 3 veces el 0x59

0x58 = 88
0x89 = 89

Con estos valores vemos lo siguiente


SQL> select chr(88) from dual;

C
-
X

SQL> select chr(89) from dual;

C
-
Y

De esta forma sabemos que en el registro que insertamos, la fecha se guarda entre 58585803 y 59595903 y la parte que nos interesa correspondiente a la fecha es la siguiente

01647807 01010101

0x01 = 1 = Mes 1
0x64 = 100 = Año 00
0x78 = 120 = Siglo 20
0x07 = 7 = Longitud de 7 bytes
0x01 = 1 = Hora 00
0x01 = 1 = Minuto 00
0x01 = 1 = Segundo 00
0x01 = 1 = Día 1

De esta forma sabemos que Oracle, se use o no un formato, siempre guarda un tipo de datos fecha y no un formato. y que Oracle los datos que guardan son:


  • Siglo

  • Año

  • Mes

  • Dia

  • Hora

  • Minuto

  • Segundo




  • y todo esto ya sea insertando una fecha 'dd/mm/yyyy hh24:mi:ss' o simplemente 'dd/mm/rr'.

    martes, 17 de junio de 2008

    Carga del sistema y excesivos context switches




    Muchas veces creemos que todos los problemas de desempeño en Oracle se encuentran dentro de la instancia, es decir, algún query mal afinado, una estructura de memoria mal definida, etc...

    En alguna ocasión, tras haber realizado un upgrade de 9.2.0.6 a 10.2.0.2 en un servidor Linux, sucedió algo que no se esperaba y que en las "Pruebas de estrés" (que fueron casi nulas) no se detectó.

    El problema era el siguiente, la carga del servidor, al empezar a recibir múltiples conexiones de oracle se elevaba a un 80% de consumo de CPU de systema, el encolamiento en CPU llegaba a 120 puntos de carga y el sistema de forma global se sentía completamente lento.

    La configuración era la siguiente

    Base de datos: 10.2.0.2 con CPUs aplicados.
    Servidor: Red Hat 4, 40gb de memoria 32-bit
    SGA: 16gb con INDIRECT_DATA_BUFFERS sobre ramfs

    Se pudo comprobar que la carga del equipo se elevaba si se generaban conexiones de forma simultánea con los siguientes scripts.

    prueba.sql


    select * from dual;
    exit;


    conexiones.sh


    #!/usr/bin/ksh
    export ORACLE_HOME=/u01/oracle/product/10.2.0
    export ORACLE_SID=ORCL

    sqlplus / as sysdba @prueba.sql &
    sqlplus / as sysdba @prueba.sql &
    sqlplus / as sysdba @prueba.sql &
    sqlplus / as sysdba @prueba.sql &
    sqlplus / as sysdba @prueba.sql &
    sqlplus / as sysdba @prueba.sql &
    sqlplus / as sysdba @prueba.sql &
    sqlplus / as sysdba @prueba.sql &
    sqlplus / as sysdba @prueba.sql &
    sqlplus / as sysdba @prueba.sql &
    sqlplus / as sysdba @prueba.sql &
    sqlplus / as sysdba @prueba.sql &
    sqlplus / as sysdba @prueba.sql &
    sqlplus / as sysdba @prueba.sql &
    sqlplus / as sysdba @prueba.sql &



    Previo a enviar el script conexiones.sh, la carga del sistema era similar a esta


    oracle@server1:~> w
    12:36:53 up 13 days, 20:09, 5 users, load average: 0.64, 0.45, 0.39


    Una vez ejecutado el script


    oracle@server1:~> nohup ./conexiones.sh

    oracle@server1:~> nohup: appending output to `nohup.out'

    oracle@server1:~> w
    12:36:53 up 13 days, 20:09, 5 users, load average: 110.22, 30.57, 4.31



    Una vez que teníamos nuestro test case, lo intentamos reproducir en algún otro equipo similar con menor SGA (sin uso de INDIRECT_DATA_BUFFERS) y la carga del servidor no incrementaba drásticamente.

    Se hizo una prueba en producción y se disminuyó el SGA para poder usar la memoria sin necesidad de ramfs. La prueba del script funcionó correctamente, y de esta forma utilizamos el Workaround, pero el db_block_buffers de 8gb se redujo a menos de 1gb
    en el db_cache_size, y por lo mismo sólo podía ser aceptado como un workaround.

    Se mandaron varios "strace" para ver en qué perdía el tiempo o generaba carga de sistema la conexión de oracle, y el resultante fueron dos funciones a nivel sistema operativo.

    mmap y remap_file_pages

    La parte de remap_file_pages se solucionó con un parche a nivel RDBMS, pero la mejoría fue muy poca, digamos que el sistema mejoró en un 5% su desempeño.

    La parte de mmap, se solucionó con una variable a nivel sistema operativo

    DISABLE_MAP_LOCK=1

    Esta variable debe de estar puesta en la sesión que levanta la base de datos Oracle y por supuesto en la sesión que levante el listener. La mejora en este caso fue de un 80%.

    El workaround quedó atrás y con esta variable, el sistema volvió a la normalidad. Al parecer este problema es un backport de un bug a la versión 10.2.0.1 y se corrigió nuevamente en la versión 10.2.0.3

    Muchas veces los problemas de desempeño van relacionados a bugs, o a configuraciones específicas, esta es una de las razones por las cuales Oracle nos invita a llevar siempre nuestros RDBMSs a las versiones más nuevas.

    jueves, 12 de junio de 2008

    Bind Variables



    Mucho se ha dicho sobre los bind variables y los problemas que pueden causar en una aplicación.

    Hay muchas entradas en asktom que ejemplifican perfectamente las ventajas sobre el uso de bind variables.

    Me he llegado a encontrar con muchas aplicaciones que fueron desarrolladas para SQL server y que los proveedores simplemente cambian el código de la aplicación sin tener en cuenta las consideraciones mínimas sobre las mejores prácticas de programación en Oracle.

    Oracle tiene un mecanismo para convertir los valores literales en variables bind. La forma de hacerlo es alterando el sistema o sesión para poner el parámetro "CURSOR_SHARING" en similar o en force. Antes de intentar este cambio, consulten bien la documentación de oracle para saber las implicaciones que esto pueda tener.


    La forma en que funciona es la siguiente...



    SQL> SELECT /*+PRUEBA 1*/ 1234 from dual;

    1234
    ----------
    1234

    SQL> SELECT /*+PRUEBA 1*/ 4321 from dual;

    4321
    ----------
    4321

    SQL> select sql_text,executions
    2 from v$sql
    3 where sql_text like 'SELECT%PRUEBA%';

    SQL_TEXT EXECUTIONS
    -------------------------------------------------- ----------
    SELECT /*+PRUEBA 1*/ 4321 from dual 1
    SELECT /*+PRUEBA 1*/ 1234 from dual 1


    Ahora ponemos el cursor_sharing en similar...

    SQL> alter session set cursor_sharing=similar;

    Sesión modificada.

    SQL> SELECT /*+PRUEBA 2*/ 1234 from dual;

    1234
    ----------
    1234

    SQL> SELECT /*+PRUEBA 2*/ 4321 from dual;

    4321
    ----------
    4321

    SQL> select sql_text,executions
    2 from v$sql
    3 where sql_text like 'SELECT%PRUEBA%';

    SQL_TEXT EXECUTIONS
    -------------------------------------------------- ----------
    SELECT /*+PRUEBA 2*/ :"SYS_B_0" from dual 2
    SELECT /*+PRUEBA 1*/ 4321 from dual 1
    SELECT /*+PRUEBA 1*/ 1234 from dual 1


    Como se puede ver, funciona de maravilla, no?. La realidad es que no siempre sucede esto...


    SQL> DECLARE numero NUMBER;
    2 BEGIN
    3 SELECT
    4 /*+PRUEBA 3*/ 1234
    5 INTO numero
    6 FROM dual;
    7 END;
    8 /

    Procedimiento PL/SQL terminado correctamente.

    SQL> select sql_text,executions
    2 from v$sql
    3 where sql_text like 'SELECT%PRUEBA%';

    SQL_TEXT EXECUTIONS
    -------------------------------------------------- ----------
    SELECT /*+PRUEBA 2*/ :"SYS_B_0" from dual 2
    SELECT /*+PRUEBA 3*/ 1234 FROM DUAL 1
    SELECT /*+PRUEBA 1*/ 4321 from dual 1
    SELECT /*+PRUEBA 1*/ 1234 from dual 1


    Como se puede observar, a pesar de que se tiene cursor_sharing en similar o incluso force, el código no cambia de literal a bind variable.

    Para esto existe una solución...


    SQL> create or replace procedure usa_bind(num number)
    2 is
    3 numero number;
    4 BEGIN
    5 SELECT
    6 /*+PRUEBA 4*/ num
    7 INTO numero
    8 FROM dual;
    9 END;
    10 /

    Procedimiento creado.

    SQL> begin
    2 usa_bind(4444);
    3 end;
    4 /

    Procedimiento PL/SQL terminado correctamente.

    SQL> select sql_text,executions
    2 from v$sql
    3 where sql_text like 'SELECT%PRUEBA%';

    SQL_TEXT EXECUTIONS
    -------------------------------------------------- ----------
    SELECT /*+PRUEBA 2*/ :"SYS_B_0" from dual 2
    SELECT /*+PRUEBA 3*/ 1234 FROM DUAL 1
    SELECT /*+PRUEBA 1*/ 4321 from dual 1
    SELECT /*+PRUEBA 4*/ :B1 FROM DUAL 1
    SELECT /*+PRUEBA 1*/ 1234 from dual 1


    Como se puede observar, la entrada como "PRUEBA 4" ya está usando un bind variable a través de un procedimiento. Pero esto nos genera un nuevo problema...

    SQL> begin
    2 usa_bind(1234);
    3 end;
    4 /

    Procedimiento PL/SQL terminado correctamente.

    SQL>
    SQL> exec usa_bind(3214);

    Procedimiento PL/SQL terminado correctamente.

    SQL> select sql_text,executions
    2 from v$sql
    3 where upper(sql_text) like '%USA_BIND%';

    SQL_TEXT EXECUTIONS
    -------------------------------------------------- ----------
    begin usa_bind(4444); end; 1
    BEGIN usa_bind(3214); END; 1
    begin usa_bind(1234); end; 1


    Así que esto nos pone en el mismo lugar en el que empezamos. Aparentemente esta es una funcionalidad de oracle y no un bug. Todo está documentado en la nota de Metalink 285447.1. Donde nos indica que el código que está entre un begin y un end, no será modificado de literal a bind.

    Pero existe aún una solución a este problema.

    SQL> call usa_bind(4444);

    Llamada terminada.

    SQL> call usa_bind(1234);

    Llamada terminada.

    SQL> call usa_bind(1111);

    Llamada terminada.

    SQL> call usa_bind(2222);

    Llamada terminada.

    SQL> select sql_text,executions
    2 from v$sql
    3 where upper(sql_text) like '%CALL USA_BIND%';

    SQL_TEXT EXECUTIONS
    -------------------------------------------------- ----------
    call usa_bind(:"SYS_B_0") 4




    De esta forma estaremos evitando un hard parse a la hora de mandar llamar nuestras ejecuciónes. Desgraciadamente, esto sugiere un cambio en la programación y por lo mismo muchas veces imposible si el código no está disponible