En esta entrada de Blog voy a intentar explicar a grandes rasgos como funcionan los tablespaces de UNDO y mostraré una solución a un crecimiento indiscriminado del tablespace de UNDO que llenó un filesystem e hizo tambalear el sistema.
Es bastante común encontrarse con que el tablespace de UNDO, debido a que algún proceso se cuelgue, empiece a crecer de manera indiscriminada y si a esto le sumas que se tenga configurado en autoextend=ON con un límite mayor de la capacidad libre existente en ese momento en el disco donde se encuentre el datafile del tablespace, circunstancia, que puede provocar que el disco se llene y por tanto, un posible crash del sistema.
Para evitar esto, un correcto dimensionamiento del tablespace UNDO nos puede evitar más de un quebradero de cabeza. Empezaré con un poco de teoría para situarnos ….
El tablespace de UNDO es lo que se conoce como un tablespace de sistema. Como lo son el de SYSTEM, SYSAUX o el TEMP. El objetivo de un tablespace de UNDO es almacenar información que permita deshacer cambios, crear imágenes antiguas de bloques de datos e incluso almacenar información útil que la funcionalidad del Flashback pueda requerir en algún momento. El tablespace UNDO debe crearse como UNDO en el proceso de creación y puede perfectamente hacerse con 2 datafiles y en filesystems distintos entre sí y distintos de donde esté ubicado el tablespace TEMP.
Ejemplo muy básico de creación de UNDO:
CREATE UNDO TABLESPACE undotbs01 DATAFILE '/undotbs01.dbf' SIZE 200M AUTOEXTEND ON MAXSIZE 8192M;
El tablespace de tipo UNDO debe existir para que la base de datos pueda almacenar allí los registros propios “de deshacer”. Si no existe, la instancia usará el tablespace SYSTEM para tal fin. Cosa nada recomendable y que es más que probable que en el archivo ALERT_.log se genere el error:
ORA-01552: cannot use system rollback segment for non-system tablespace ‘%s’.
Por otro lado, existe un parámetro de inicialización “UNDO_TABLESPACE” que permite indicarle a la instancia cual es el tablespace UNDO a utilizar, útil en los casos en los que existen varios tablespaces UNDO.
¿ Como gestiona ORACLE el tablespace UNDO ?
Oracle lo puede gestionar de dos maneras distintas:
1.- SMU (System Managed Undo)- (AUTO*) o lo que es lo mismo, modo automático de gestión, donde no se utilizan los segmentos de rollback externos, la información de UNDO se almacena en un tablespace especial dedicado exclusivamente a este objetivo.
2.- RBU (modo manual de gestión del UNDO) – (MANUAL*) . Se almacena de forma externa en segmentos de rollback. Este era el único método que existía en versiones anteriores de Oracle.
Ambos modos de gestión se establecen mediante el parámetro dinámico UNDO_MANAGEMENT que libera a los DBA´s de la administración y monitoreo si lo configuras en modo AUTO.
SQL> alter system set undo_management = <valor>;
Ejemplo para setear el parámetro en modo AUTO:
SQL> alter system set undo_management = AUTO;
¿ Cómo ver que valores de UNDO tenemos configurados en la BBDD ?
SQL> show parameters undo
NAME TYPE VALUE ------------------------------------ ----------- -------------------------- undo_management string AUTO undo_retention integer 900 undo_tablespace string UNDOTBS1
¿ Que significan ?
undo_management
explicado en el punto anterior.
undo_retention
Parámetro dinámico que indica en segundos (900 por defecto) cuanto tiempo ha de permanecer, al menos, la información de UNDO disponible.
Ej: alter system set undo_retention=<valor>;
undo_tablespace
Parámetro dinámico que indica el tablespace de almacenamiento de “UNDO” a usar en el arranque.
Hasta aquí un poco de teoría, pondré el problema que me he encontrado alguna vez y su solución y ampliaré información durante la explicación para entender mejor otros parámetros que no he explicado anteriormente.
Problema: El Tablespace de UNDO crece indiscriminadamente y llega a llenar el filesystem donde estaba ubicado el datafile asociado. Este crecimiento indiscriminado puede ser debido a una transacción muy grande, un import de alguna tabla muy grande, etc…
Solución:
Volver a poner en el tamaño adecuado el tablespace de UNDO una vez realizada la transacción. Lamentablemente, no se puede reducir un tablespace de UNDO, tenemos que crear un nuevo tablespace tipo undo con el tamaño deseado, asignar este a la BBDD, y eliminar el antiguo. Luego si queremos volver a ponerlo todo igual, repetiremos la operación creando de nuevo otro tablespace tipo undo con el mismo nombre que tenía originalmente el primer tablespace undo y asignárselo a la BBDD. Finalmente borraremos el auxiliar creado. Por último, comprobaremos que realmente el tablespace de UNDO está correctamente definido.
NOTA: Realmente NO es que no se pueda reducir el tablespace de undo haciendo un RESIZE. Lo cierto es que si se puede, pero sólo hasta donde lo permita la HWM (High water mark).
Pasos para solucionar el problema:
Como el datafile llenó el filesystem, lo primero que tenía que hacer es hacer espacio en el filesystem, pero asegurándome de que cuando haga espacio, el tablespace de UNDO no siga creciendo de manera que lo vuelva a llenar. Para ello, primero quitaré el autoextend del tablespace UNDO.
Conectado como sys o system ….
SQL> ALTER DATABASE DATAFILE '/oracle/oradata/SID/undotbs01.dbf' AUTOEXTEND OFF;
Una vez asegurado de que el datafile no crecerá más, haremos espacio en el filesystem. Como se trata del filesystem donde están ubicados los tablespaces de sistema de Oracle como system, temp, etc... y no tenía otros ficheros que pudiese eliminar para hacer espacio. Lo que hice fue reducir el tamaño del tablespace TEMP que posteriormente, cuando ya haya conseguido volver a redefinir el tablespace UNDO volveré a poner a su tamaño original.
SQL> ALTER DATABASE TEMPFILE '/oracle/oradata/SID/temp01.dbf' RESIZE 2048M;
NOTA: Reducir TEMP a un tamaño que nos permita ganar espacio en el filesystem, pero que permita también trabajar a la base de datos.
Ahora podríamos probar de intentar reducir el TS UNDO con resize a ver si Oracle nos lo permite. Esto nos ahorraría el trabajo de crear el auxiliar, etc…. Pero tenemos que saber que tamaño nos deja reducirlo por encima de la HWM. (En mi caso, no lo conseguí, y tuve que crear el TS UNDO auxiliar).
Imaginemos que quiero reducir el datafile a 1000MB …
SQL> ALTER DATABASE DATAFILE ‘/oracle/oradata/SID/undotbs01.dbf’ RESIZE 1000M;
Al ejecutar la instrucción aparece el siguiente error:
ORA-03297: file contains used data beyond requested RESIZE value
Pues me veo obligado a crearme el TS Auxiliar para poder reducir el TS de Undo….
CONSULTAS ÚTILES RELACIONADAS CON TODAS LAS TAREAS A REALIZAR
Primero saber el tamaño actual del tablespace de UNDO desde SQLPLUS:
SQL> SELECT Sum(v$datafile.bytes / 1024 / 1024) AS "Tamaño Actual TS UNDO [MB]" FROM v$datafile INNER JOIN v$tablespace ON v$datafile.ts# = v$tablespace.ts# INNER JOIN dba_tablespaces ON v$tablespace.NAME = dba_tablespaces.tablespace_name WHERE dba_tablespaces.contents = 'UNDO' AND dba_tablespaces.status = 'ONLINE' /
Podemos visualizarlo también directamente desde la consola de OEM:
Iremos a la pestaña “servidor” y pulsamos en el enlace “Gestión automática de deshacer” y sale esta pantalla donde podemos ver la información del tablespace Undo incluído su tamaño actual.
La siguiente consulta nos puede ayudar a determinar un valor adecuado para el TS Undo
SELECT Substr(e.VALUE,1,25) "UNDO RETENTION [Sec]", d.undo_size / (1024 * 1024) "Tamaño Actual UNDO [MByte]", (To_number(e.VALUE) * To_number(f.VALUE) * g.undo_block_per_sec) / (1024 * 1024) "Tamaño Optimo UNDO [MByte]" FROM (SELECT Sum(a.bytes) undo_size FROM v$datafile a, v$tablespace b, dba_tablespaces c WHERE c.contents = 'UNDO' AND c.status = 'ONLINE' AND b.NAME = c.tablespace_name AND a.ts# = b.ts#) d, v$parameter e, v$parameter f, (SELECT Max(undoblks / ((end_time - begin_time) * 3600 * 24)) undo_block_per_sec FROM v$undostat) g WHERE e.NAME = 'undo_retention' AND f.NAME = 'db_block_size' /
Devuelve algo como:
UNDO RETENTION [Sec] Tama??o Actual UNDO [MByte] ------------------------- --------------------------- Tama??o Optimo UNDO [MByte] --------------------------- 900 12288 1779.82031
Aún así, yo recomiendo crearlo con el tamaño original e ir controlando durante el trabajo habitual de la BBDD que necesidades tiene el UNDO.
Seguimos … Creamos el nuevo TS Undo auxiliar con el tamaño mínimo de este óptimo sacado anteriormente, por ejemplo.
SQL>CREATE UNDO TABLESPACE "UNDOTBS_AUX" DATAFILE ‘/oracle/oradata/undotbs_aux.dbf’ SIZE 1800M;
Ahora debemos establecerlo en la BBDD. (Yo prefiero hacerlo desde OEM), pero desde SQL se hace así:
SQL> ALTER SYSTEM SET UNDO_TABLESPACE=’UNDOTBS_TEMP’ scope=both;
Ahora ya podríamos eleminar el primer tablespace de Undo (“el original” que creció desmesuradamente):
SQL> DROP TABLESPACE UNDOTBS1 INCLUDING CONTENTS AND DATAFILES;
Aquí nos puede pasar dos cosas, una que lo borre sin problemas y otra es que nos dé el siguiente error:
ERROR at line 1:
ORA-30013: undo tablespace ‘UNDOTBS1′ is currently in use
Pues tenemos que poner offline los segmentos de rollback que están online en el UNDO. Para saber que segmentos son, usaremos la siguiente consulta:
SELECT owner, segment_name, status FROM dba_rollback_segs WHERE tablespace_name='UNDOTBS1' ORDER BY 3;
Sale algo como:
OWNER SEGMENT_NAME STATUS ------ ------------------------------ ---------------- PUBLIC _SYSSMU1_3129330276$ OFFLINE PUBLIC _SYSSMU2_1303119469$ OFFLINE PUBLIC _SYSSMU3_1479839839$ OFFLINE PUBLIC _SYSSMU4_3642025306$ OFFLINE PUBLIC _SYSSMU10_1545059069$ OFFLINE PUBLIC _SYSSMU6_1524158477$ OFFLINE PUBLIC _SYSSMU7_1101254083$ OFFLINE PUBLIC _SYSSMU8_4223133350$ OFFLINE PUBLIC _SYSSMU9_3952001453$ OFFLINE PUBLIC _SYSSMU5_2891492079$ ONLINE
10 rows selected.
Pues para cambiar el estado haremos:
SQL> ALTER ROLLBACK SEGMENT "_SYSSMU5_2891492079$" OFFLINE;
Seguidamente volveremos a ejecutar la sentencia:
SQL> DROP TABLESPACE UNDOTBS1 INCLUDING CONTENTS AND DATAFILES; Tablespace dropped.
y no debería dar problemas.
Ahora tan sólo deberíamos volver a crear el tablespace de UNDO con el nombre original y tamaño original antes del incidente y por último, volver a asignar a la Base de datos este tablespace de UNDO que crearemos. (Y en este ejemplo, también volveremos a poner el tamaño que tenía originalmente al tablespace TEMP usando “resize”). Se hace así:
SQL> CREATE UNDO TABLESPACE "UNDOTBS1" DATAFILE ‘/oracle/oradata/SID/undotbs01.dbf’ SIZE 1000M;
Tablespace created.
SQL> ALTER SYSTEM SET UNDO_TABLESPACE=’UNDOTBS1' SCOPE=BOTH;
System altered.
SQL> DROP TABLESPACE UNDOTBS_TEMP INCLUDING CONTENTS AND DATAFILES;
Tablespace dropped.
NOTA: si no deja borralo, proceder como en el ejemplo anterior.
SQL> show parameters undo
NAME TYPE VALUE
------------------------------------ ------- --------
_undo_autotune boolean TRUE
undo_management string AUTO
undo_retention integer 3000
undo_tablespace string UNDOTBS1
CONSULTAS ÚTILES RELACIONADAS CON EL UNDO
La siguiente consulta busca una sesión de usuario que está en el undo para poderla matar…
column username format a30 SELECT a.name,b.status , d.username , d.sid , d.serial# FROM v$rollname a,v$rollstat b, v$transaction c , v$session d WHERE a.usn = b.usn AND a.usn = c.xidusn AND c.ses_addr = d.saddr AND a.name IN ( SELECT segment_name FROM dba_segments WHERE tablespace_name = 'UNDOTBS1' );
Para matar la sesión ejecutaremos: SQL> alter system kill session '147,4'; donde 147 es el SID y 4 el SERIAL#
# Con la siguiente query podemos ver el estado de los segmentos de UNDO: # UNEXPIRED significa que estos segmentos de UNDO no contienen ninguna transacción activa, # pero estos contienen transacciones que todavía son requeridos para FLASHBACK. # EXPIRED significa que estos segmentos no son requeridos después del periodo de retención definido en undo_retention. # ACTIVE significa que estos segmentos de UNDO contienen transacciones activas, o sea, no se ha realizado commit. # Los valores son en MB. SELECT SYSDATE AS fecha, unexpired.unexpired, expired.expired, active.active FROM (SELECT Sum(bytes / 1024 / 1024) AS unexpired FROM dba_undo_extents WHERE status = 'UNEXPIRED') unexpired, (SELECT Sum(bytes / 1024 / 104) AS expired FROM dba_undo_extents tr WHERE status = 'EXPIRED') expired, (SELECT CASE WHEN Count(status) = 0 THEN 0 ELSE Sum(bytes / 1024 / 1024) END AS active FROM dba_undo_extents WHERE status = 'ACTIVE') active /
# Buscar la sesión que está colgada o pendiente de ponerse a OFFLINE en TS UNDO para luego matarla set lines 10000 column name format a10 SELECT a.name,b.status FROM v$rollname a,v$rollstat b WHERE a.usn = b.usn AND a.name IN ( SELECT segment_name FROM dba_segments WHERE tablespace_name = 'UNDOTBS1' );
# Transacciones activas en TS UNDO SELECT NAME, XACTS "ACTIVE TRANSACTIONS" FROM V$ROLLNAME, V$ROLLSTAT WHERE STATUS = 'PENDING OFFLINE' AND V$ROLLNAME.USN = V$ROLLSTAT.USN;