jueves, 12 de julio de 2007

High Water Mark (HWM)

Generalmente, cuando se crea una tabla o un índice... se crea un segmento asociado al objeto. El segmento se crea con determinados bloques del data file, pero muy poco espacio es destinado para nuestro uso. Cuando los datos van llenando los bloques asignados al segmento, se van alocando más bloques para que usemos.
A medida que los bloques van siendo alocados, la HWM se posiciona en el último bloque para mostrarnos la cantidad total de bloques alocados hasta el momento y estaban disponibles para ser usados.
Un error común es pensar que a medida que eliminamos datos de los bloques, la HWM se refresca para mostrarnos que estamos utilizando menos bloques; pero la realidad es que la HWM se mantiene siempre apuntando al último bloque alocado.
Sabemos que cuando realizamos un Full Scan, son leídos secuencialmente todos los bloques de la tabla hasta la HWM, por lo cual, debemos tener en claro que aunque eliminemos datos de una tabla, ese espacio "vacío" se va a seguir leyendo, lo cual trae como consecuencia la lectura de bloques innecesarios.

Veamos un ejemplo:


SQL_9iR2> CREATE TABLE emp
2 (id NUMBER , sexo VARCHAR2(1 ) ;

Table created.

SQL_9iR2> INSERT INTO emp
2 SELECT level , 'M'
3 FROM dual
4 CONNECT BY level <= 800000 ;

800000 rows created.

SQL_9iR2> INSERT INTO emp
2 SELECT level , 'F'
3 FROM dual
4 CONNECT BY level <= 200000 ;

200000 rows created.

SQL_9iR2> ANALYZE TABLE emp COMPUTE STATISTICS ;

Table analyzed.

SQL_9iR2> SET AUTOTRACE TRACEONLY

SQL_9iR2> SELECT sexo
2 FROM emp
3 WHERE id = 900000 ;

Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=160 Card=10000 Bytes=60000)
1 0 TABLE ACCESS (FULL) OF 'EMP' (Cost=160 Card=10000 Bytes=60000)

Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
1655 consistent gets
0 physical reads
0 redo size
213 bytes sent via SQL*Net to client
363 bytes received via SQL*Net from client
1 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
0 rows processed

SQL_9iR2> DELETE FROM emp
2 WHERE sexo = 'M' ;

800000 rows deleted.

SQL_9iR2> SET AUTOTRACE TRACEONLY

SQL_9iR2> SELECT sexo
2 FROM emp
3 WHERE id = 900000 ;

Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=160 Card=10000 Bytes=60000)
1 0 TABLE ACCESS (FULL) OF 'EMP' (Cost=160 Card=10000 Bytes=60000)

Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
1655 consistent gets
419 physical reads
0 redo size
213 bytes sent via SQL*Net to client
363 bytes received via SQL*Net from client
1 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
0 rows processed


Como pudimos ver, aunque hayamos eliminados datos de la tabla, seguimos leyendo la misma cantidad de bloques que antes porque la HWM no se refrescó y sigue apuntando al último bloque alocado en el segmento.
Para solucionar éste problema, podemos realizar...

1) TRUNCATE ...
- Si la tabla esta vacia podemos hacer un TRUNCATE para resetear la HWM. Sino, podemos hacer un export de los datos, luego truncar la tabla y realizar un import.
2) ALTER TABLE ... MOVE
- De ésta manera reorganizamos la tabla. Tener en cuenta que luego de ejecutar el ALTER, hay que hacer un REBUILD de todos los índices de la tabla!
3) Dropear y recrear el objeto (export/import)
4) ALTER TABLE ... SHRINK SPACE | SHRINK SPACE COMPACT
- Para 10g en adelante.

Apliquemos a nuestro ejemplo el punto 2...


SQL_9iR2> ALTER TABLE emp MOVE ;

Table altered.

SQL_9iR2> ANALYZE TABLE emp COMPUTE STATISTICS ;

Table analyzed.

SQL_9iR2> SET AUTOTRACE TRACEONLY

SQL_9iR2> SELECT sexo
2 FROM emp
3 WHERE id = 900000 ;

Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=33 Card=2000 Bytes=12000)
1 0 TABLE ACCESS (FULL) OF 'EMP' (Cost=33 Card=2000 Bytes=12000)

Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
335 consistent gets
0 physical reads
0 redo size
213 bytes sent via SQL*Net to client
363 bytes received via SQL*Net from client
1 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
0 rows processed


Como vemos, la HWM se refresco y ahora estamos leyendo sólo los bloques que contienen nuestros datos.

12 comentarios:

Anónimo dijo...

Gracias, tu explicacion de lo que es el HWM es muy clara y concisa y me sirvio de gran ayuda.

Alberto Frydman dijo...

Te felicito por la explicacion Leonardo, muy clara.

Anónimo dijo...

Leonardo, como me puedo comunicar contigo o lanzarte una consulta acerca de compresion de tablas en Oracle 10g??

Leonardo Horikian dijo...

Buenas, puedes hacerme una consulta acerca de compresión de tablas en Oracle 10g a través de éste Blog insertando un comment.
Saludos.

Anónimo dijo...

Hola Leonardo:

primero para felicitarte de todos los tips que nos escribes en tu página, están muy claros, por otro lado, donde o como consigo el explains ya que lo he buscado en donde esta instalado oracle y no xiste, podrías mandarmelo por correo a: ray_garcia66@hotmail.com, te lo agradecere infinitamente
Gracias y Saludos
RAY MÉXICO

Leonardo Horikian dijo...

Hola Ray,

- Para crear la tabla PLAN_TABLE, hay que ejecutar el script
UTLXPLAN.sql ubicado en $ORACLE_HOME/rdbms/admin

- Para ver el Explain Plain, hay que ejecutar el script
UTLXPLS.sql (para ejecuciones en serial) o
UTLXPLP.sql (para ejecuciones en paralelo) ubicado en
$ORACLE_HOME/rdbms/admin

Saludos.

Anónimo dijo...

Hola, Leonardo.
Gran blog en general y gran explicación del High Water Mark en particular. Me ha sido muy util para mejorar el rendimiento de una bbdd 9i. Tengo una pregunta: ¿Cómo se hace un move en tablas particionadas? O mejor, ¿cómo se reorganizan tablas que están particionadas?
Muchas gracias y un saludo,
Angel.

Leonardo Horikian dijo...

Hola Angel,

Para hacer un MOVE en tablas particionadas puedes guiarte por el siguiente ejemplo:

SQL> CREATE TABLE estudiantes
2 (
3 legajo NUMBER(5),
4 nombre VARCHAR2(30),
5 ingreso DATE
6 )
7 PARTITION BY RANGE(ingreso)
8 (
9 PARTITION ingreso_2006 VALUES LESS THAN(TO_DATE('01/01/2007','DD/MM/YYYY'))
,
10 PARTITION ingreso_2007 VALUES LESS THAN(TO_DATE('01/01/2008','DD/MM/YYYY'))

11 ) ;

Table created.

SQL> ALTER TABLE estudiantes MOVE PARTITION ingreso_2006;

Table altered.


Saludos

Anónimo dijo...

Leonardo, tengo la siguiente consulta: Dispongo de 2 esquemas ( X1 , Y1). En X1 se realizan todas las transacciones.En el esquema Y1 se copia el contenido (aprox. 920M ) de la tabla 'TxDia' del esquema X1 de manera diaria. He creado dicha tabla 'TxDia' en el esquema Y1 con storage ( initial 900M next 5M ) ya que es lo que aprox. pesa en el esquema X1. Diariamente en el esquema Y1 la tabla TxDia se tiene que truncar antes de copiar el contenido. Mi consulta es ya que se realiza en el esquema Y1 un truncado y luego una copia de contenido (insert into ..select) desde otra tabla, estas tareas serían mas optimas si ejecuto el truncate con la opción de reuse storage.

Saludos

Luis

Leonardo Horikian dijo...

Luis,

Si, podría beneficiarte la opción de REUSE STORAGE para el insert que realizas diariamente. Pero me gustaría darte una idea para ver si podés resolver esto de la carga de datos diaria de alguna otra manera. Supongamos que la tabla del esquema X1 se carga diariamente con 2 millones de datos. En vez de truncar diariamente la tabla del esquema Y1 para realizar el insert con los datos de esa tabla, y si, la gran mayoria de los datos que vas a insertar ya se encontraban en la tabla TxDia, podés utilizar Vistas Materializadas para capturar sólo los datos "que cambiaron" y luego copiar "sólo" esos datos. No se si ésto te ayuda, pero es sólo una idea que a lo mejor te sea útil.

Saludos

gangsta dijo...

Master

¿Cual es la mejor método de Reducir el tamaño de una tabla?
¿y Bajar la HWM?

Mi tabla pesa 3gb, el export e import no me sirve se demora 36 horas, y solo tenemos 24 horas para realizar dicho proceso.

Leonardo Horikian dijo...

Hola Gangsta,

Esto dependerá de la versión de base de datos en la cual estes trabajando.

Probaste los métodos 2 y 4 que coloqué en el post?

Saludos