Mostrando entradas con la etiqueta oracle. Mostrar todas las entradas
Mostrando entradas con la etiqueta oracle. Mostrar todas las entradas

viernes, 5 de septiembre de 2008

Operadores de Conjunto (caso práctico)

En la compañía, se está desarrollando una aplicación para un cliente. Ésta sirve para "clonar" esquemas dentro de una base de datos, con la flexibilidad de decidir los objetos, campos, constraints, etc. que quieres llevarte.

Una problemática que se presentó, fue la siguiente:

¿Cómo saber qué constraints se pueden replicar con una tabla que no se migra con todas las columnas?

Suponiendo que tenemos una tabla original con 3 campos, y la tabla destino, sólo contiene dos campos. Imaginemos un constraint de tipo "check" que valida que el (campo1 = 'true') or (campo2 = 'false') or (campo3 = 'lo que sea'). No podemos replicar ese constraint al nuevo esquema, ya que marcaría un error. Así que ¿cómo pudiéramos revisar, basado en nuestras columnas seleccionadas, los constraints que se pueden migrar sin problema?

La solución se puede lograr fácilmente con lógica de conjuntos



WITH registros AS
(SELECT --+materialize
constraint_name,
column_name
FROM dba_cons_columns
WHERE TABLE_NAME = 'SDO_COORD_REF_SYS')
SELECT constraint_name,
COUNT(1) numero
FROM registros
WHERE column_name IN('GEOG_CRS_DATUM_ID',
'SOURCE_GEOG_SRID')
GROUP BY constraint_name
INTERSECT
SELECT constraint_name,
COUNT(1)
FROM registros
GROUP BY constraint_name;


Primero explicaré brevemente sobre la clausula WITH.

La clausula with, que nace con Oracle 9i Release 1, en lugar de repetir código complejo, o pesado, nos permite darle un nombre, y reusar la referencia a través del nombre múltiples veces dentro de una sentencia SQL. Forma parte del estándar SQL-99. Jonathan Lewis, adicionalmente incluye el hint de "materialize" en las cláusulas with, ya que de esta forma, se materializa el resultado en una tabla temporal y al seleccionarse en múltiples partes, el resultado materializado se accede de forma más rápida.

En este ejemplo, como sé que voy a trabajar con los constraints de una sola tabla, mi universo de datos son todos los registros contenidos en "dba_cons_columns" para la tabla en cuestión. De ahí el with


WITH registros AS
(SELECT --+materialize
constraint_name,
column_name
FROM dba_cons_columns
WHERE TABLE_NAME = 'NOMBRE_TABLA')


De ahí, necesito obtener los constraints que incluyan mis columnas que voy a replicar, o por lo menos alguna de las columnas. Añado un "count" para saber cuántas de mis columnas son incluidas en el constraint.


SELECT constraint_name,
COUNT(1) numero
FROM registros
WHERE column_name IN('CAMPOS',
'A',
'BUSCAR')
GROUP BY constraint_name


Finalmente para terminar, necesito saber cuántas columnas en total tiene cada constraint.


SELECT constraint_name,
COUNT(1)
FROM registros
GROUP BY constraint_name


Teniendo esos sets de datos, usamos lógica de conjuntos y hacemos una intersección con la palabra "INTERSECT"


SQL> WITH registros AS
2 (SELECT --+materialize
3 constraint_name,
4 column_name
5 FROM dba_cons_columns
6 WHERE TABLE_NAME = 'SDO_COORD_REF_SYS')
7 SELECT constraint_name,
8 COUNT(1) numero
9 FROM registros
10 WHERE column_name IN('GEOG_CRS_DATUM_ID',
11 'SOURCE_GEOG_SRID')
12 GROUP BY constraint_name
13 INTERSECT
14 SELECT constraint_name,
15 COUNT(1)
16 FROM registros
17 GROUP BY constraint_name;

CONSTRAINT_NAME NUMERO
------------------------------ ----------
COORD_REF_SYS_FOREIGN_GEOG 1

SQL>


Esto nos da el resultado que si voy a incluir sólo las columnas 'GEOG_CRS_DATUM_ID', 'SOURCE_GEOG_SRID', sólo puedo llevarme el contraint COORD_REF_SYS_FOREIGN_GEOG.

Pasando ya de lleno a la lógica de conjuntos, Oracle cuenta con tres operadores de conjuntos

UNION [ALL]
MINUS
INTERSECT

Se pueden usar para unir varios queries y llevar a cabo la operación de conjunto deseada, llevan orden de precedencia a menos que vengan entre paréntesis.


SQL> SELECT *
2 FROM
3 (SELECT 1 FROM dual
4 UNION ALL
5 SELECT 1 FROM dual)
6 INTERSECT
7 (SELECT 1 FROM dual
8 UNION ALL
9 SELECT 2 FROM dual)
10 ;

1
----------
1



Las expresiones correspondientes en las sentencias select, deben de coincidir en número y tipo, si no se sigue esto, oracle genera un error.

Hay algunas observaciones por considerar.

No se pueden usar operadores de conjuntos en columnas de tipo BLOB, CLOB, BFILE, VARRAY, o "nested tables".

No son válidos en columnas de tipo long.

Los operadores MINUS, UNION e INTERSECT terminan agrupando los resultados. Union All no agrupa y sólo junta los resultados de las dos fuentes de datos.

Y la más importante de todas las consideraciones es que Oracle para apegarse a un estándar, cambiará (no sé si el release 11g ya lo tenga) la precedencia del comando INTERSECT para que se evalue primero, por esta razón, Oracle recomienda siempre usar paréntesis cuando se use "INTERSECT".

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