miércoles, 23 de mayo de 2007

Problemas de TNS utilizando DB links

Al definir un DB link y utilizarlo en una consulta, puede obtenerse el error:

ORA-12154: TNS:could not resolve service name

Nada parece estar mal:
  • Creamos el link utilizando el service name del tnsnames.ora
  • Verificamos que la entrada del servicio está justamente definida en el archivo
  • Revisamos que nuestra aplicación esté leyendo el tnsnames.ora correcto (en caso de que tengamos muchos)
  • El usuario y password del link funciona OK si me conecto por SQL*plus
  • Tengo el privilegio CREATE DATABASE LINK
  • Al crear el link me retorna que el link fue creado exitosamente
Al utilizar el link en un select, se obtiene el error mencionado.
La razón por la cual no encuentra el conector aún la desconozco, pero la forma de evitar el problema es:

En lugar de definir el link de esta forma:

create public database link uat1prod_lnk
connect to oramain
identified by "0racl3"
using 'produat1db';

donde 'produat1db' es el nombre del service name para ese conector (tnsnames.ora)

Definirlo así:

create public database link uat1prod_lnk
connect to oramain
identified by "0racl3"
using '(description=(address=(protocol=TCP)
(host=192.168.3.32)(port=1521))(connect_data=(sid=uat1db)))';


donde 'uat1db' es el nombre del SID de la base de datos.

Crearlo de esta manera corrige el problema. Otra ventaja adicional que me proporciona esta sintaxis, es independencia del tnsnames.ora.
Lo único que necesito es: IP, puerto y SID de la base de datos.

viernes, 18 de mayo de 2007

PL/SQL haragán

¿Es PL/SQL 'lazy' para las comparaciones?
Es decir, para evaluar (expr_a AND expr_b): si expr_a es falso entonces ¿se evalúa expr_b?
Asimismo en (expr_a OR expr_b): si expr_a es verdadero entonces ¿se evalúa expr_b?

Opción 1 -> consultar la documentación

Opción 2 -> ¡probarlo!

SQL> create table t (a int);
Table created.
SQL> create or replace function fA return boolean is
2 begin
3 insert into t values (1);
4 return (false);
5 end;
6 /

Function created.

SQL> begin
2 if ((1=0) and fA) = true then
3 NULL;
4 end if ;
5 end;
6 /

PL/SQL procedure successfully completed.

SQL> select * from t;

no rows selected

Conclusión: PL/SQL es lazy. No se insertó un 1 en la tabla ya que la función fA nunca se ejecutó.

¿Y en SQL? ¿Son las condiciones 'lazy' en las consultas?


Lo podemos probar con otra función similar:

SQL>
create or replace function fB return number is
2 begin
3 insert into t values (0);
4 return (0);
5 end;
6 /


En SQL no podría ejecutar esta función, ya que realiza una operación DML:

SQL> select * from dual where ((0 = 0) and (fB = 0));

ERROR at line 1:
ORA-14551: cannot perform a DML operation inside a query
ORA-06512: at "EMP01.FB", line 3


Sin embargo, si cambiamos AND por OR, notamos que la función fB ni siquiera es evaluada:


SQL> select * from dual where ((0 = 0) or (fB = 0));


D
-
X

1 row selected.

Conclusión: SQL también es lazy. Si se hubiera evaluado la segunda expresión, entonces tendría que haber retornado el error ORA-14551.

domingo, 13 de mayo de 2007

ORA-01461: can bind a LONG value only for insert into a LONG column

Este error puede ocurrir cuando se insertan o actualizan valores en la base de datos desde alguna aplicación cliente, como por ejemplo Forms o Java.

En mi caso utilizando Forms 10g, intentaba grabar en un textbox dos mil y tantos caracteres en un formulario, obteniendo repetidamente el error. El campo de formulario estaba definido como CHAR de largo máximo 4000 y la columna de la base de datos era VARCHAR2 de 4000; entonces donde estaba el problema?

El problema resultó en el character set de la base de datos y el character set del cliente.
La base de datos tenía un character set multibyte, es decir que puede utilizar varios bytes para representar un caracter. El cliente (en mi caso Application Server 10g), estaba configurado con un character set single byte. Resultado: el cliente enviaba 1 byte y la base de datos almacenaba en 3 bytes. Al enviar más de la tercera parte del tamaño de la columna, se produce un bug (conocido) de Oracle en el cual supone que se le está enviando un LONG. Forms internamente utiliza bind variables para las operaciones en formularios, por lo tanto se produce el error: can bind a LONG value only for insert into a LONG column.

La solución simple es modificar el character set del cliente, en este caso del Application Server. Particularmete elegí el mismo que tenía la base de datos (UTF8).

Los datos quedan intactos, y el cambio en mi cliente es transparente. Ahora cliente y servidor tienen los mismos juegos de caracteres, y evita que se produzcan las conversiones que llevan al bug mencionado.

Para conocer el character set de la base de datos, puede ejecutarse esta consulta desde SQL*Plus:

SELECT VALUE FROM v$nls_parameters WHERE PARAMETER = 'NLS_CHARACTERSET';

jueves, 10 de mayo de 2007

Manipulando HTML en SQL*plus

"Necesito ejecutar unos scripts SQL*plus que insertan líneas de código HTML en una tabla. El problema que tengo es que cuando los valores contienen el símbolo ampersand, Oracle me pide que ingrese un valor. Como hago para evitarlo?"

Es común en HTML encontrar cosas del estilo:

<font><B>Acentuaci&oacute;n en la Gram&aacute;tica</B></font>

El símbolo ampersand en SQL*Plus suele utilizarse como prefijo standard para indicar un parámetro de sustitución en un script.

Por lo pronto, el único problema que presenta el código HTML en SQL*Plus, es para escribir el símbolo ampersand (suerte que las comillas en HTML son dobles y no simples).

Bueno, quien me planteó el problema no tenía intenciones de modificar los scripts originales, así que le di una solución simple: crear un script que invoque a los demás (con @), y que al comienzo del mismo contenga la línea:

SET DEFINE OFF

Este comando de SQL*Plus inhibe los parámetros de entrada en SQL*Plus, por lo cual va a ignorar todos los ampersand que encuentre.

Aunque la solución es simple y funciona para muchos casos, es también un poco drástica: inhibe los verdaderos parámetros que contengan nuestros scripts. Si nuestro script recibe un parámetro &valor y en la siguiente línea inserta un texto que contiene un &aacute, entonces no hay manera de que SQL*plus se de cuenta cuál es parámetro y cuál no. Conclusión: los scripts deben alterarse.

Para esto hay algunas alternativas. La primera, es cambiar el prefijo para definir parámetros, por algún símbolo que no sea caracter especial en HTML, por ejemplo, el símbolo de pesos.

SET DEFINE $

De esta manera los ampersand de HTML serán ignorados.

Una segunda alternativa, es utilizar el caracter de escape (por defecto '\') para 'escapar' los ampersand del código. Luego los ampersand no son incluídos al guardarse en la tabla. Por lo general tendremos muchos menos parámetros que símbolos ampersand, así que particularmente elijo la primer opción.

En conclusión, cambiar el prefijo de parámetros con SET DEFINE $ es la opción más flexible. Usar SET DEFINE OFF al comienzo del script nos inhibe los parámetros, pero si no los necesitamos, se transforma en la opción más sencilla de implementar.

martes, 8 de mayo de 2007

Fallo de librerías en instalación de 10g en SUSE 10

Talvez hayan tenido este mismo problema.
Mientras se instala Oracle 10.2.0 en Suse 10.0 64 bits, el Oracle Universal Installer falla en un 64% de completado, cuando esta enlazando las librerías. Al mirar el log generado, aparece lo siguiente:

INFO: ./x86_64-suse-linux/bin/ld: skipping incompatible /usr/lib64/gcc-lib/x86_64-suse-linux/3.3.3/../../../libpthread.so when searching for -lpthread
/usr/lib64/gcc-lib/x86_64-suse-linux/3.3.3/../../../../x86_64-suse-linux/bin/ld: skipping incompatible /usr/lib64/gcc-lib/x86_64-suse-linux/3.3.3/../../../libpthread.a when searching for -lpthread
/usr/lib64/gcc-lib/x86_64-suse-linux/3.3.3/../../../../x86_64-suse-linux/bin/ld: cannot find -lpthread

INFO: collect2:
INFO: ld returned 1 exit status

INFO: make: *** [ctxhx] Error 1

El problema que ocurre es que Oracle necesita algunas librerías de 32 bits, y la instalación por defecto de Suse tiene todas de 64 bits. Es necesario instalar las de 32 también.

Son estas dos:

1) glibc-devel-2.3.4-2.25 (i386)
2) compat-libstdc++-33-3.2.3-47 (x86_64)

Si alguna de estas librerías están disponibles en versiones más nuevas, no hay que elegirlas, hay que instalar exactamente las versiones que acabo de marcar. Las versiones más nuevas no funcionan.
Asimismo, si ya estuviera instalada la librería compat-libstdc++-33-3.2.3-47.3, hay que desinstalarla e instalar la que sirve: compat-libstdc++-33-3.2.3-47.

Realmente es el único problema serio que me encontré, el resto mas o menos está en el manual de instalación.

martes, 1 de mayo de 2007

Mis índices no funcionan!

TOP FIVE de casos en los cuales los índices que creamos 'inexplicablemente' no funcionan.

1 - CONSULTAS QUE RETORNAN MUCHOS REGISTROS
Este es el caso de consultas que retornan muchos registros. Según algunos autores, más del 5 por ciento del total de filas, pero en realidad es un porcentaje variable que se infiere según varios parámetros como cardinalidad, cantidad de filas, distribución en los bloques, etc.
En este tipo de operaciones, es mas 'barato' para la base de datos leer toda la tabla y colocarla en memoria que acceder a cada registro mediante el índice. Hay que pensar que un índice es una estructura más que debemos consultar para recién llegar al bloque que contiene nuestro registro y traerlo a memoria, por lo cual ese acceso trae un costo adicional para cada fila. Si consultamos pocos registros, el incremento del costo por acceso a índice se absorve con el tiempo total. Si consultamos muchos registros, el overhead incrementa significativamente el tiempo de completitud.
No está mal que los índices no se usen y se produzcan 'FULL SCAN TABLES'. El optimizador sabe los elementos que tiene a mano, cual es el costo asociado a cada método y por lo tanto puede resolver qué usar en cada caso.

2 - SE APLICAN FUNCIONES SOBRE COLUMNAS INDIZADAS
Cuando aplicamos cualquier función en una columna indizada en la cláusula WHERE, el índice deja de tener efecto.
Un simple TRUNC sobre una columna fecha, o un TRIM en una columna VARCHAR2, deja sin efecto a los índices sobre esas columnas.

SELECT nombre, TRIM(apellido)
FROM socios
WHERE SUBSTR(codigo,1,3) = 'DNO'
AND apellido > 'P';

En este ejemplo, un índice sobre la columna codigo no es de utilidad, ya que la función SUBSTR lo anula. Un índice sobre apellido puede funcionar sin problemas ya que no hay ninguna función aplicada en la cláusula WHERE (notar que sí la hay en el SELECT).

3 - CONVERSIONES IMPLICITAS
Por efecto del punto anterior, hay algunos casos en los que no tenemos en cuenta los tipos de datos en el WHERE, y dejamos que se realicen conversiones automáticas. Esto no es preferible, ya que Oracle siempre convierte la columna al tipo del elemento comparante.

Por ejemplo, una consulta como la siguiente:

SELECT nombre, TRIM(apellido)
FROM socios
WHERE nro_socio = '2232';

internamente es traducida como:

SELECT nombre, TRIM(apellido)
FROM socios
WHERE TO_CHAR(nro_socio) = '2232';

...y el índice sobre nro_socio es inhibido por la conversión implícita.

Siempre es bueno convertir explícitamente este tipo de condiciones en donde intervienen diferentes tipos de datos.
Otras opciones son: estandarizar el almacen de datos (por ejemplo guardando siempre los valores con la función aplicada), usar una función de conversión sobre el literal en lugar de en la columna, crear índices de función.

4 - EL PREDICADO NO ES ADECUADO
Por ejemplo, tenemos un índice compuesto formado por las columnas a, b y c. Se quiere realizar una consulta incluyendo en la cláusula WHERE la condicion b (por la cual queremos usar el índice), pero no se incluye a!
Aunque la columna a almacene el mismo valor para todos los registros, Oracle necesita explícitamente todas las columnas previas entre las condiciones del where. De la misma manera, si queremos usar una condición sobre c, deberíamos incluir en el where condiciones para a y b.
Otro caso es cuando tenemos un OR en el where, un distinto != sobre la columna indizada, o cuando la columna aparece a ambos lados de una expresión.

5 - ESTADISTICAS OBSOLETAS (O INEXISTENTES)
El optimizador de la base de datos construye un plan de ejecución basado enteramente en estadísticas de los objetos, almacenadas en el dicionario de datos. La recolección de estas estadísticas debe realizarse periódicamente para que Oracle sepa cuales son los costos asociados a cada método de acceso a los registros, y de esta manera elegir la mejor opción. Al insertar y eliminar datos frecuentemente, cambian las estadísticas quedan obsoletas, y esto puede traer aparejado un impacto directo en la performance de las consultas.

Pueden recolectarse estadísticas de todo el esquema, de una tabla, de un índice, del sistema. El paquete DBMS_STATS contiene procedimientos para hacer el trabajo: GATHER_TABLE_STATS, GATHER_INDEX_STATS, GATHER_SCHEMA_STATS.
Para saber si una tabla tiene estadísticas, puede utilizarse la siguiente consulta:

SELECT last_analyzed
FROM user_tables
WHERE table_name = 'mi_tabla';

Retorna la fecha de la ultima recolección. Si retorna NULL, entonces nunca fueron recolectadas.

Ver también:
Indices condicionales
11g y sus índices invisibles
Indices de función para mejorar un LIKE