Oracle - Apuntes sobre LOBs para DBAs




Objetivo

Este documento recopila información que considero interesante conocer sobre los LOBs (en la documentación oficial los llaman formalmente como “Large Objects”, y a partir de la 11.1 como “Basic Files”), sobre todo a la hora de la administración. Lógicamente no sustituye a la documentación oficial, sólo son unos apuntes que he ido recopilando de varias fuentes y he preferido juntarlo en un documento único.

No hablo para nada de tablas particionadas ni de IOT´s. Esto en la documentación oficial.

Introducción

¿Qué son? ¿Para qué se utilizan? ¿Clases de LOBs? ¿Características?

Todo esto mejor en la documentación oficial, pero haré un resumen esquemático.


Estructura

Cuando se crea una tabla conteniendo una columna de tipo LOB, se crean dos segmentos para contener la columna LOB especificada.

Estos segmentos son de tipo LOBSEGMENT y LOBINDEX. El segmento LOBINDEX se utiliza para acceder a las páginas/trozos (chunks) que se almacenan en el segmento LOBSEGMENT.

Cuando creamos una columna LOB, en general lo que se almacena en la fila es un puntero, o localizador LOB (LOB locator). Este localizador es lo que la aplicación recupera. Cuando solicitamos los bytes desde el 1000 al 2000 del LOB, el localizador se utiliza contra el LOBINDEX para encontrar dónde están esos bytes almacenados, y entonces se accede al LOBSEGMENT. El LOBINDEX se utiliza para encontrar las piezas del LOB más fácilmente. Un LOB se almacena en “chunks” o piezas, y cualquier pieza es accesible por nosotros.


Ejemplo 1: Creamos una Tabla T1 con una columna de tipo clob.

Si consultamos la USER_SEGMENTS vemos que tenemos 3 segmentos, el segmento de datos de la propia tabla, y dos segmentos para la columna de tipo CLOB:

Existe la vista DBA_LOBS (USER_LOBS/ALL_LOBS) dónde también podemos consultar más información:


Los nombres de los segmentos los ha generado el sistema (se puede asignar un nombre), e incluyen el Object_ID de la tabla, y un nº de columna. Además distingue el tipo de segmento de que se trata (SYS_IL / SYS_LOB):

Ejemplo 2: Tabla T2 con dos columnas de tipo clob.

Esto se complicaría con las tablas particionadas, ya que cada partición tendría sus propios segmentos LOBSEGMENT y LOBINDEX, pero este tema mejor verlo en la documentación oficial.

Antes de seguir, aquí va un truco. Como hemos visto, hemos utilizado una sentencia de CREATE TABLE muy simple, sin indicar parámetros de almacenamiento, pero realmente internamente no es tan simple. Podemos utilizar el paquete dbms_metadata para obtener la DDL completa, lo cual puede ser muy útil para saber lo que ocurre:

Select dbms_metadata.get_ddl(‘TABLE’,’T1’) from dual;

Select dbms_metadata.get_ddl(‘TABLE’,’T2’) from dual;

LOB tablespace.

Como hemos podido ver en la sentencia completa devuelta por el paquete DBMS_METADATA, el tablespace dónde reside la tabla en sí puede ser diferente del tablespace dónde se almacene el logsegment y lobindex. Es decir, el tablespace que almacena los datos de LOB puede ser separado y distinto del tablespace que almacena los datos de la tabla.

Las razones principales a considerar para utilizar diferentes tablespaces para los datos de LOB y los de la tabla son principalmente relativos a la administración y al rendimiento. Desde el punto de vista de la administración, un tipo de datos LOB representa una cantidad de información considerable (en cuanto a tamaño). Si la tabla tuviera millones de filas, y cada fila tuviera un LOB considerable asociado, los datos LOB podrían ser enormes. Tiene sentido separar la tabla de los datos de LOB simplemente para facilitar el backup y recovery, y la gestión del espacio. Por ejemplo se podría querer tener un tamaño de extensiones uniformes para los datos de LOB diferente del de la tabla regular.

La otra razón podría ser rendimiento I/O. Por defecto, los LOBs no son cacheados en el buffer cache. Por tanto, por defecto cada acceso LOB, sea lectura o escritura, es una I/O física, una lectura directa desde disco o escritura directa a disco. (NOTA: en los LOB “inline” si se cachean, lo veremos).

Dado que cada acceso es un acceso I/O físico, tiene sentido separar a sus propios discos aquellos objetos que sepamos seguro que experimentarán más I/O.

Es importante saber que el LOBINDEX y el LOBSEGMENT siempre van en el mismo tablespace. No se pueden separar en distintos tablespaces. Esto es así a partir de la 8iR3. De hecho, todas las características de almacenamiento del LOBINDEX son heredadas del LOBSEGMENT. Es más, un usuario no puede hacer DROP ni REBUILD. El LOBINDEX no puede ser alterado.

Almacenamiento in-line/out-of-line.

Las columnas LOB guardan localizadores (punteros) que referencian al valor actual del LOB. En función de las propiedades de las columnas (especificadas en la creación de la tabla), y dependiendo del tamaño del LOB, los valores actuales del LOB son almacenados en la misma fila de la tabla (in-line) o fuera de la fila de la tabla (out-of-line) en el segmento LOBSEGMENT.

Los valores o datos del LOB son almacenados out-of-line:

· Cuando se especifica explícitamente con la clausula DISABLE STORAGE IN ROW en la creación de la tabla.

· Cuando el tamaño del LOB es mayor que 3964 bytes (independientemente de las propiedades de la columna)

· Si se actualiza un LOB que está almacenado out-of-line y el LOB resultante es más pequeño de 3964 bytes, aún así sigue almacenado como out-of-line.

Los valores LOB son almacenados in-line cuando:

· Por defecto. Es decir, si cuando se crea la tabla no se especifica ningún parámetro en la clausula de almacenamiento del LOB. (Creo que en la documentación oficial de oracle 10.2 este punto lo tiene mal)

· Se especifica explícitamente la clausula ENABLE STORAGE IN ROW cuando se crea la tabla, y el tamaño del LOB almacenado en la fila dada es pequeño, 4K o menos.

· El valor del LOB es NULL (independientemente de las propiedades de almacenamiento de la columna)

Utilizar las propiedades de almacenamiento por defecto (in-line storage) puede dar mejor rendimiento, ya que evita la sobrecarga de crear y manejar almacenamiento “out-of-line” para valores LOB pequeños. Si los valores de LOB almacenados son con frecuencia pequeños, se recomienda almacenamiento in-line. Lo veremos a continuación con un ejemplo de Tom Kyte.

La sentencia CREATE TABLE de nuestro ejemplo anterior incluía lo siguiente:

Esto controla si los datos de LOB se almacenan siempre separadamente de la tabla, en el LOBSEGMENT, o si puede a veces ser almacenado en la misma tabla sin situarse en el LOBSEGMENT. Si ENABLE STORAGE IN ROW está establecido, a diferencia de DISABLE STORAGE IN ROW, los LOBs pequeños de hasta 4000 bytes serán almacenados en la propia tabla, parecido a como lo haría un VARCHAR2. Sólo cuando el LOB exceda los 4000 bytes será mudado “out of line” al LOBSEGMENT.

Habilitar el almacenamiento en la fila, es lo de por defecto, y en general, debería ser la forma correcta si se sabe que los LOBs generalmente van a caber en la propia tabla. Por ejemplo, se podría tener una aplicación con un campo DESCRIPCION, que podría almacenar entre 0 y 32Kb de datos, podría ser más, pero generalmente 32 Kb o menos. La mayoría de las descripciones se sabe que son muy cortas, un par de cientos de caracteres. En vez de sufrir la sobrecarga de almacenarlo “out of line” y acceder a través del índice cada vez que se recuperan, se pueden almacenar “inline”, en la propia tabla. Además, si el LOB está usando el valor por defecto NOCACHE (los datos del LOBSEGMENT no es cacheado en el buffer cache), entonces un LOB almacenado en la tabla evitará la I/O física requerida para recuperar el LOB.

Ejemplo: Creamos una tabla con dos campos clob, uno con ENABLE STORAGE IN ROW, y el otro con DISABLE STORAGE IN ROW, es decir, un campo puede almacenar los datos en la misma tabla y el otro no.

Insertamos algunos datos de tipo cadena de menos de 4000 bytes.

Y ahora leemos cada fila, y utilizando el paquete DBMS_MONITOR, habilitamos SQL_TRACE, para ver el rendimiento en cada caso:

Se genera una traza en el udump, que tratamos con tkprof. Este es el resultado:

La recuperación de la columna IN_ROW fue significativamente más rápida y consumió menos recursos. Utilizó 13155 operaciones de I/O lógica, mientras que la columna OUT_ROW utilizó el doble, debido al acceso al segmento LOBINDEX para encontrar las piezas del LOB. Además, se puede ver que para la columna OUT_ROW, la recuperación de las 4385 filas implica 4385 operaciones de I/O físicas y el mismo número de esperas de “direct path read”. Esto se debe a las lecturas de los datos de LOB no cacheados. Se podrían reducir en el caso de habilitar el cacheo de los datos de LOB, pero entonces hay que asegurarse de tener suficiente buffer caché adicional. Si fueran LOBs realmente grandes podríamos no querer que los datos se cachearan.

El almacenamiento in-row/out-of-row afectará tanto a las modificaciones como a las lecturas.

Clausula CHUNK

Los LOBs se almacenan en chunks (piezas); el índice que apunta a los datos LOB, apuntan a chunks individuales de datos. Los chunks son conjuntos de bloques lógicos contiguos y constituyen la unidad de asignación más pequeña para los LOBs. El tamaño de CHUNK debe ser un entero múltiplo del tamaño de bloque oracle (máximo 32 Kbytes). Cada instancia LOB (cada valor de LOB almacenado out-of-line) consumirá al menos un chunk. Un único chunk es utilizado por un único valor de LOB. Si una tabla tiene 100 filas y cada fila tiene un LOB con 7Kb de datos, habrá 100 chunks ocupados. Si el tamaño de chunk es de 32Kb, tendremos 100 chunks de 32Kb asignados. La cuestión es, un chunk es utilizado por sólo una entrada LOB (dos LOBs no utilizarán el mismo chunk). Por ejemplo, si tienes una tabla con una media de 7Kb de LOBs, y utilizas un chunk de 32kb, estaremos desperdiciando aproximadamente 25k de espacio por instancia LOB. En cambio, si utilizas un chunk de 8Kb, el desperdicio de espacio se minimiza.

También hay que tener cuidado cuando se quiere minimizar el nº de chunks que se tienen por instancia LOB. Como hemos visto, hay un LOBINDEX utilizado para apuntar a los chunks individuales, y cuantos más hay, mayor es el índice. Si tenemos un LOB de 4MB y utilizamos un chunk de 8Kb, necesitaremos al menos 512 chunks para almacenar esa información. Eso significa que es necesario al menos tantas entradas de LOBINDEX para apuntar a esos chunks. Puede no parecer mucho, pero hay que recordar que es por instancia LOB, por tanto si tenemos cientos de LOBs de 4Mb, tendremos cientos de entradas. Esto afectará al rendimiento, ya que lleva más tiempo leer y manejar muchos chunks pequeños que leer unos pocos, pero más grandes.

El objetivo final es utilizar el tamaño de chunk de tal manera que se minimice el “desperdicio” de espacio, pero que también se proporcione un eficiente almacenamiento de los datos.

Recordad: no se puede cambiar el tamaño del CHUNK.

Lo que recomienda oracle:

Para LOBs menores de 32K, un tamaño de chunk que sea el 60% (o más) del tamaño de LOB sería un buen punto de partida. Para LOBs más grandes de 32K, elegir un tamaño de chunk igual al tamaño de actualización más frecuente.

Clausula PCTVERSION

Se utiliza para controlar la consistencia en lectura del LOB (que es diferente a la consistencia en lectura normal).

El LOBSEGMENT no utiliza UNDO para registrar sus cambios; en vez de eso, versiona la información directamente en el propio LOBSEGMENT.

El LOBINDEX genera undo igual que cualquier otro segmento, pero el LOBSEGMENT no. Cuando se modifica un LOB, Oracle asigna un nuevo chunk y deja el viejo chunk ahí. Si se hace rollback de la transacción, los cambios al índice de LOB son deshechos y el índice apuntará al viejo chunk otra vez. Por tanto el mantenimiento de UNDO es llevado a cabo directamente en el propio segmento LOB. A medida que se modifican los datos, los datos viejos se dejan ahí y los nuevos datos se van creando. Esto es importante cuando se leen los datos LOB. Los LOBs son consistentes en lectura, igual que todos los demás segmentos lo son. Si se recupera un localizador LOB a las 9:00 am, los datos LOB que recuperamos de él serán de esa hora, aunque venga alguien y modifique los datos de LOB y los valide (o no). Oracle utiliza el LOBSEGMENT junto con la vista consistente en lectura del LOBINDEX para deshacer los cambios al LOB, para presentar los datos como existían cuando se recuperó el localizador LOB.

Surgen dos preguntas: si no se utiliza el UNDO y los LOBs soportan la consistencia en lectura, ¿cómo prevenir el error ORA-1555: snapshot too old?, y ¿cómo controlar el espacio usado por las versiones viejas? El parámetro PCTVERSION controla el porcentaje de espacio del LOB reservado para los datos de LOB versionados (antiguos). Por defecto es el 10%, adecuado para la mayoría de los casos, ya que lo normal es insertar y recuperar, actualizar no es lo normal. Lo normal es insertar una vez y recuperar muchas veces. Pero si se tiene una aplicación que modifica los LOBs con frecuencia, ese valor puede ser muy pequeño si se hacen lecturas frecuentes al mismo tiempo que otra sesión los modifica. Si se obtiene un ORA-22924 cuando se procesa un LOB, la solución no es incrementar el tamaño del tablespace de UNDO, o incrementar el undo retention, sería incrementar el PCTVERSION:

ALTER TABLE tabname MODIFY LOB (lobname) (PCTVERSION n);

Clausula RETENTION

Este parámetro es mutuamente excluyente con el anterior (PCTVERSION). En vez de reservar un % de espacio en el LOBSEGMENT para versionar, la clausula RETENTION retiene los datos basándose en tiempo, no en porcentaje. Es necesario que la base de datos tenga el parámetro UNDO_RETENTION para especificar cuánto tiempo retener la información de undo para consistencia en lectura. Este parámetro también aplicará a los datos LOB en este caso. No se puede especificar el tiempo de retención utilizando esta clausula; es heredada del propio parámetro UNDO_RETENTION.

Clausula CACHE

Controla si los datos del LOBSEGMENT son o no almacenados en el buffer cache. El valor por defecto NOCACHE implica que cada acceso será una lectura directa al disco.

Las opciones son NOCACHE, CACHE o CACHE READS.

CACHE READS permite a los datos LOB leidos ser cacheado, pero las escrituras serán directas a disco.

CACHE permite tanto lecturas como escrituras cacheadas.

Para modificarlo:

ALTER TABLE tabname MODIFY LOB (lobname) (CACHE);

ALTER TABLE tabname MODIFY LOB (lobname) (NOCACHE);

Para LOBs pequeños o medianos, cachearlos tiene sentido. En una carga grande de muchos LOBs, también. Para un LOB de 50MB de tamaño, si embargo, no tiene sentido tenerlo en la cache.

Hay que tener en cuenta el uso del pool KEEP o RECYCLE, en vez de cachear en la cache por defecto.

Clausula LOB STORAGE

La sentencia de creación CREATE TABLE tiene una completa clausula de almacenamiento para controlar las características físicas de almacenamiento.

Aplica tanto al LOBSEGMENT como al LOBINDEX.

Si tenemos un tablespace manejado localmente (locally-managed), las configuraciones relevantes para un LOB serían FREELIST, FRELIST GROUPS y BUFFER_POOL.

Ejemplos de Operaciones más comunes sobre LOBs

Definiendo el tablespace y los parámetros de almacenamiento para LOBs persistentes.

Se puede indicar explícitamente el tablespace y las características de almacenamiento para cada columna de tipo LOB persistente, por ejemplo:

Modificando el tablespace y los parámetros de almacenamiento para LOBs persistentes.

Para modificar los parámetros, con la clausula MODIFY LOB de la sentencia ALTER TABLE. Sólo algunos parámetros pueden modificarse. También se puede cambiar el tablespace con ALTER TABLE … MOVE. Sin embargo, una vez que la tabla ha sido creada, no se puede cambiar el tamaño del CHUNK, o la configuración ENABLE/DISABLE STORAGE IN ROW.

Ejemplos:

ALTER TABLE test MODIFY LOB (lob1) STORAGE ( NEXT 4M MAXEXTENTS 100 PCTINCREASE 50);

ALTER TABLE test MOVE TABLESPACE tbs1 LOB (lob1,lob2) STORE AS (TABLESPACE tbs2 DISABLE STORAGE IN ROW);

Comentarios

Entradas populares de este blog

[ORACLE] Script Espacio ocupado y libre de tablespaces (incluyendo los temporary)

Oracle - Traza 10046 (SQL_TRACE)