Vous êtes sur la page 1sur 35

TEMA 4: ndices y dems

NOTA: Todas las pruebas de este estudio se harn bajo el usuario SCOTT y usando el programa de Oracle SQL* Plus como interfaz. ste se inicia tecleando en una ventana MS-DOS: sqlplusw.exe. Previamente se habr concedido a este usuario los roles SELECT_CATALOG_ROLE y PLUSTRACE para que pueda ver el diccionario de datos como usuario DBA mediante el comando: GRANT select_catalog_role,plustrace TO SCOTT;

Los ndices.Oracle proporciona varios tipos diferentes de ndices para que usemos. Brevemente son los siguientes: "B*Tree", son los ndices convencionales. Similares a un rbol binario, proporcionan un rpido acceso por clave a una fila individual o rango de ellas, requiriendo normalmente pocas lecturas para encontrarla/ s. Es importante destacar aqu que la letra "B" de "B* Tree" no significa "binary" sino "balanceado". Un ndice "B*Tree" tiene los siguientes subtipos: o o o tablas organizadas por ndice ( I OT) , stas son tablas almacenadas en una estructura "B*Tree" como vimos en un tema anterior; ndices de cluster "B* Tree", son usados por los cluster por ndice para ordenar la clave de cluster como vimos en un tema anterior; ndices descendentes, permiten ordenar los datos "de grande a pequeo" y pueden ser tiles para evitar que se realice una ordenacin del tipo ORDER DESC final (esto no significa que al usar un ndice de estos la consulta ya no precise del ORDER DESC); ndices de clave inversa, ndices "B* Tree" en donde los bytes de la clave estn en orden inverso, consiguiendo as distribuciones ms uniformes. Suelen dar mejor rendimiento que otros tipos al trabajar con secuencias o con fechas;

"Bitmap", normalmente en un ndice "B*Tree" hay una relacin "uno-a-uno" entre una entrada de ndice y una fila, en cambio, en los ndices "Bitmap" una nica entrada de ndice apunta a varias filas simultneamente; "Bitmap Join", proporciona un medio para desnormalizar datos en una estructura de ndice en vez de en una tabla; de tipo funcin, son ndice "B*Tree" o "Bitmap" que almacenan el resultado calculado de una funcin sobre la fila de una columna/s, no los datos de la columna en si. Podemos considerar este tipo de ndices como un ndice sobre una columna virtual, en otras palabras, una columna que no est fsicamente almacenada en la tabla. Pueden ser usado para acelerar consultas que tienen la forma SELECT * FROM t WHERE FUNCTION(DATABASE_COLUMN) = SOME_VALUE, ya que el valor FUNCTION(DATABASE_COLUMN) ya ha sido calculado y almacenado en el ndice; del dominio de una aplicacin, son ndices que construimos y almacenamos nosotros mismos en Oracle o incluso fuera de Oracle. Le decimos al optimizador cmo de selectivo es nuestro ndice y cmo de costoso es ejecutarlo y el optimizador decidir cuando usar o no nuestro ndice basado en esas informaciones. El "Oracle Text index" es un ejemplo de ndice del dominio de una aplicacin y usa un grupo de tablas para implementar ese concepto de ndice.

ndices de tipo funcin.Los ndices de tipo funcin fueron aadidos a Oracle 8.1.5 y ahora en Oracle9i ver.2 son parte de la Standard Edition, antes no (no obstante hay que estar alerta porque en segn que versiones son necesarias las activaciones de ciertos parmetros como QUERY_REWRITE, etc.). Nos dan la habilidad de indexar columnas calculadas y usar esos ndices en una sentencia. En pocas palabras esta capacidad nos permite hacer bsquedas "case-insensitive" u ordenaciones, buscar con ecuaciones complejas y extender la eficiencia del lenguaje SQL implementando nuestras propias funciones y operadores, para buscar sobre ellos. Hay varias razones por las que podemos querer usar un ndice basado en funcin, de las cuales las dos principales seran estas: Son fciles de implementar y proporcionan un valor inmediato. Pueden ser usadas para acelerar las aplicaciones existentes sin cambiar su lgica o sus consultas.

Sencillo ejemplo de ndice basado en funcin Consideremos el siguiente ejemplo. Queremos usar una bsqueda "case-insensitive" en la columna ENAME de la tabla EMP. Antes de la venida de los ndices de tipo funcin hubiramos aadido una columna extra a la tabla EMP llamada UPPER_ENAME por ejemplo. Esta columna hubiera sido mantenida por un disparador INSERT y UPDATE el cual simplemente declarara NEW.UPPER_NAME := UPPER(:NEW.ENAME). Esta nueva columna tendra un ndice para finalizar. Pero con los ndices de tipo funcin el proceso es bastante ms sencillo: CREATE TABLE emp2 TABLESPACE users AS SELECT * FROM emp WHERE 1=0; INSERT INTO emp2 (empno,ename,job,mgr,hiredate,sal,comm,deptno) SELECT ROWNUM empno, INITCAP(SUBSTR(object_name,1,10)) ename, SUBSTR(object_type,1,9) JOB, rownum MGR, created hiredate, ROWNUM SAL, ROWNUM COMM, (MOD(ROWNUM,4)+1)*10 DEPTNO FROM all_objects WHERE ROWNUM < 10000; CREATE INDEX emp2_upper_idx ON emp2(UPPER(ename)) TABLESPACE users; exec dbms_stats.gather_table_stats (user,'EMP2',cascade=>true) Ahora cualquier aplicacin que lance una consulta "case-insensitive" como sta har uso del ndice, ganando en rendimiento: SET AUTOTRACE TRACEONLY EXPLAIN SELECT * FROM EMP2 WHERE UPPER(ename) = 'KING'; Execution Plan ---------------------------------------------------------0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=3 Card=2 Bytes=92) 1 0 TABLE ACCESS (BY INDEX ROWID) OF 'EMP2' (Cost=3 Card=2 Bytes=92) 2 1 INDEX (RANGE SCAN) OF 'EMP2_UPPER_IDX' (NON-UNIQUE) (Cost=2 Card=2)

Antes de que esta caracterstica estuviera disponible cada fila de la tabla EMP deba ser escaneada, pasada a maysculas y comparada. Por el contrario con un ndice en UPPER(ENAME) la consulta aplica la constante 'KING' al ndice, se hace un escaneo por rango de pocos datos y se accede a la tabla a travs del ROWID para leer los datos. Todo esto es muy rpido. En cambio, sino usamos ndices de tipo funcin sino funciones normales sobre el predicado de una consulta tenemos que esa funcin debe aplicarse a cada una de las filas de la tabla sobre la que se ejecute. Si la ejecucin de esa funcin normal sobre una fila tarda una centsima de segundo y la tabla tiene 10.000 filas podemos deducir fcilmente que esa consulta por lo menos siempre tardar al menos 10 segundos. Vamos a ver otro ejemplo de la vida real, en donde implementaremos rutina en PL/ SQL que se basa en la funcin SOUNDEX. Adems usaremos una variable global de paquete como un contador en nuestro procedimiento, lo cual nos permitir ejecutar consultan que hagan uso de mi funcin MY_SOUNDEX y as ver exactamente cuantas veces fue ejecutada:

CREATE OR REPLACE PACKAGE stats AS cnt NUMBER DEFAULT 0; END; / CREATE OR REPLACE FUNCTION my_soundex ( p_string IN VARCHAR2 ) return VARCHAR2 DETERMINISTIC AS l_return_string VARCHAR2(6) DEFAULT SUBSTR( p_string, 1, 1 ); l_char VARCHAR2(1); l_last_digit NUMBER DEFAULT 0; TYPE vcArray IS TABLE OF VARCHAR2(10) INDEX BY binary_integer; l_code_table vcArray; BEGIN stats.cnt := stats.cnt+1; l_code_table(1) := 'BPFV'; l_code_table(2) := 'CSKGJQXZ'; l_code_table(3) := 'DT'; l_code_table(4) := 'L'; l_code_table(5) := 'MN'; l_code_table(6) := 'R'; FOR i IN 1 .. LENGTH(p_string) LOOP EXIT WHEN (LENGTH(l_return_string) = 6); l_char := UPPER(SUBSTR( p_string, i, 1 ) ); FOR j IN 1 .. l_code_table.COUNT LOOP IF (INSTR(l_code_table(j),l_char) > 0 AND j <> l_last_digit) THEN l_return_string := l_return_string || TO_CHAR( j,'fm9' ); l_last_digit := j; END IF; END LOOP; END LOOP; RETURN RPAD( l_return_string, 6, '0' ); END; /

Notad en esta funcin que estamos usando la palabra clave DETERMINISTIC. Esto declara que la funcin precedente cuanda reciba las mismas entradas retornar la misma salida. Esto se necesita para crear un ndice son una funcin de usuario porque debemos decirle a Oracle que la funcin es determinista y retornar un resultado consistente dadas las mismas entradas. Le estamos diciendo a Oracle que esta funcin debe ser verificada para retornar el mismo valor, llamada tras llamada, dadas las mismas entradas. Si ste no fuera el caso podramos recibir diferentes respuestas cuando accediramos a los datos va el ndice versus un full table scan . El parmetro DETERMINISTIC implica por ejemplo que no podemos crear un ndice con la funcin DBMS_RANDOM.RANDOM, el generador de nmeros aleatorios. Sus resultados no seran determinsticos: dadas las mismas entradas obtendramos resultados aleatorios. En cambio, la funcin SQL UPPER usada en el primer ejemplo es determinista, as que podemos crear un ndice en el valor UPPER de los datos de una columna, para un valor dado siempre devolver el mismo resultado. Ahora que tenemos la funcin MY_SOUNDEX vamos a ver su rendimiento sin ndices. Usaremos la tabla EMP2 que creamos antes con unas 10.000 filas en ella: SET TIMING ON SET AUTOTRACE ON EXPLAIN SELECT ename, hiredate FROM emp2 WHERE my_soundex(ename) = my_soundex('Kings'); ENAME ---------Ku$_Chunk_ Ku$_Chunk_ HIREDATE --------10-AUG-04 10-AUG-04

Elapsed: 00:00:01.07 Execution Plan ---------------------------------------------------------0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=32 Card=100 Bytes=1900) 1 0 TABLE ACCESS (FULL) OF 'EMP2' (TABLE) (Cost=32 Card=100 Bytes=1900) SET AUTOTRACE OFF SET SERVEROUTPUT ON exec dbms_output.put_line( stats.cnt ) 19998

Podemos ver que la sentencia ha tardado alrededor de un segundo en ejecutarse y ha tenido que hacer un full table scan . La funcin MY_SOUNDEX ha sido llamada casi 20.000 veces (de acuerdo a nuestro contador), o sea, dos veces por cada fila. Vamos a ver como indexando la funcin podemos acelerar las cosas. Crearemos el ndice y calcularemos las estadsticas en cascada otra vez: CREATE INDEX emp_soundex_idx ON emp2(substr(my_soundex(ename),1,6)) TABLESPACE users; exec dbms_stats.gather_table_stats (user,'EMP2',cascade=>TRUE)

El aspecto interesante a destacar en el comando CREATE INDEX es el uso de la funcin SUBSTR. Esto es porque estamos indexando una funcin que retorna una cadena. Si estuviramos indexando una funcin que retornara un nmero o una fecha la funcin SUBSTR no hubiera sido necesaria. La razn por la que debemos usar la funcin SUBSTR es porque retornamos una cadena que puede ser de hasta VARCHAR2(4000). Eso puede ser muy grande para indexar (las entradas del ndice deben caber en las partes del tamao de un bloque). Si de todas formas lo intentramos crear sin la funcin SUBSTR obtendramos el error ORA-01450. Tambin obtendramos ese error concatenando ndices. Por eso, en las funciones que retornen una cadena debemos limitar el tipo de dato que se retornar. Por eso en este ejemplo, sabiendo que mi funcin MY_SOUNDEX retornar por un mximo de 6 caracteres, estoy limitando su salida a esos 6 primeros caracteres. Ahora estamos listos para probar el rendimiento de la tabla con el ndice de tipo funcin en ella. Queremos monitorizar, adems del efecto sobre las operaciones SELECT, el efecto del ndice sobre las operaciones INSERT. En el ejemplo anterior nuestras consultas tardaron un segundo, y las inserciones de 9.999 filas tardaron 0,5 segundos. Pero ahora con nuestro ndice tardamos 1,2 segundos en insertar las filas. Esta es la sobrecarga introducida por el manejo del nuevo ndice en la funcin MY_SOUNDEX (cualquier tipo de ndice afecta al rendimiento). Ahora testearemos la consulta, as que reejecutaremos la consulta: exec stats.cnt := 0 SET TIMING ON SET AUTOTRACE ON EXPLAIN SELECT ename, hiredate FROM emp2 WHERE SUBSTR(my_soundex(ename),1,6) = my_soundex('Kings'); ENAME ---------Ku$_Chunk_ Ku$_Chunk_ HIREDATE --------10-AUG-04 10-AUG-04

Elapsed: 00:00:00.02 Execution Plan ----------------------------------------------------------

0 1 2

0 1

SELECT STATEMENT Optimizer=ALL_ROWS (Cost=4 Card=10 Bytes=190) TABLE ACCESS (BY INDEX ROWID) OF 'EMP2' (Cost=4 Card=10 Bytes=190) INDEX (RANGE SCAN) OF 'EMP2_SOUNDEX_IDX' (NON-UNIQUE) (Cost=2 Card=10)

SET AUTOTRACE OFF exec dbms_output.put_line( stats.cnt ) 2

Si comparamos los dos ejemplos (el no indexado y el indexado) encontraremos que el INSERT fue afectado por un tiempo de ejecucin ligeramente superior al doble. Sin embargo la SELECT tard desde un segundo a cero segundos (instantneamente). Las cosas importantes a destacar aqu son las siguientes: La insercin de 9.999 filas tard aproximadamente dos veces ms. Indexando una funcin de usuario necesariamente afecta al rendimiento de las operaciones INSERT y de algunas operaciones UPDATE. Debis tener presente que cualquier ndice afectar al rendimiento, por supuesto. Por ejemplo, hice un pequeo test sin la funcin MY_SOUNDEX, indexando slo la columna ENAME. Esto caus que el INSERT tardar casi un segundo en ejecutarse (la funcin PL/ SQL no es responsable por completo de la sobrecarga). Dado que muchas aplicaciones insertan y actualizan una fila cada vez, y que cada fila tarda 1 diezmilsima de segundo en ser insertada, probablemente no nos daremos cuenta. En esas situaciones estamos pagando el precio de ejecutar cada vez la funcin sobre una columna y no los miles de veces cada vez que consultemos los datos. Mientras que la insercin fue dos veces ms lenta la consulta fue muchas veces ms rpida. Evalo la funcin MY_SOUNDEX una pocas veces en vez de 20.000 veces. La diferencia en rendimiento de nuestra consulta aqu es cuantificable y bastante grande. Adems el tamao de nuestra tabla crece, la consulta por full scan tardar ms y ms en ejecutarse. La consulta basada en ndice siempre se ejecutar con las mismas caractersticas de rendimiento cuando crezca la tabla. Tenemos que usar la funcin SUBSTR en nuestra consulta. Esto no es tan agradable como teclear WHERE MY_SOUNDEX(ename)=MY_SOUNDEX( King ) pero es fcilmente evitable como veremos brevemente.

Entonces el INSERT se vi afectado pero la consulta funcion de forma increblemente rpida. El precio vale la pena. Adems si nunca actualizamos las columnas involucradas en la funcin MY_SOUNDEX, las operaciones UPDATE no se vern penalizadas (MY_SOUNDEX slo es invocada si la columna ENAME es modificada y su valor es cambiado). Ahora veremos como hacer para que la consulta no tenga que llamar a la funcin SUBSTR. El uso de SUBSTR es susceptible de error (los otros usuarios tienen que saber que deben aplicar SUBSTR desde el carcter 1 al 6). Si ellos usan otro tamao no se usar el ndice. Adems podemos querer controlar en el servidor el nmero de bytes del ndice. Esto nos permitir reimplementar la funcin MY_SOUNDEX en el futuro con 7 bytes en vez de 6 si nos hace falta. Podemos ocultar la funcin SUBTRS con una sencilla vista como esta: CREATE OR REPLACE VIEW emp2_v AS SELECT ename, SUBSTR(my_soundex(ename),1,6) ename_soundex, hiredate FROM emp2; exec stats.cnt := 0 SET TIMING ON SET AUTOTRACE ON EXPLAIN SELECT ename, hiredate FROM emp2_v WHERE ename_soundex = my_soundex('Kings'); Execution Plan ---------------------------------------------------------0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=4 Card=10 Bytes=190) 1 0 TABLE ACCESS (BY INDEX ROWID) OF 'EMP2' (Cost=4 Card=10 Bytes=190) 2 1 INDEX (RANGE SCAN) OF 'EMP2_SOUNDEX_IDX' (NON-UNIQUE) (Cost=2 Card=10) SET AUTOTRACE OFF

exec dbms_output.put_line( stats.cnt ) 2

Vemos el mismo tipo de plan de ejecucin que obtuvimos antes con la tabla base. Todo lo que hemos hecho ha sido ocultar la funcin SUBSTR(F(X), 1, 6) en la propia vista. El optimizador todava reconoce que esta columna virtual es de hecho la columna indexada y hace lo correcto . Vemos tambin que el rendimiento es el mismo. Usar esta vista es tan bueno como usar la tabla base (incluso mejor porque ocultamos la complejidad y nos permite cambiar el tamao de SUBSTR cuando nos haga falta). Indexando slo algunas filas Adems de ayudar de forma invisible a las consultas que usan funciones como UPPER, LOWER, etc. Los ndices de tipo funcin pueden ser usados para indexar selectivamente algunas de las filas en una tabla. Como todos sabemos los ndices "B* Tree no pueden contener entradas NULL. Si tenemos un ndice I en una tabla T CREATE INDEX i ON t(a,b) y tenemos una fila en donde A y B son NULL no habr su entrada en la estructura del ndice. Esto es til cuando slo estamos indexando algunas filas de una tabla. Consideremos una gran tabla con una columna NOT NULL llamada PROCESSED_FLAG que puede tomar dos valores, Y o N, y que su valor por defecto es N. Las nuevas filas que sean aadidas con un valor de N significarn que no han sido procesadas, y las que sean actualizadas a Y indicarn que s han sido procesadas. Queremos indexar esta columna para poder recuperar las filas N rpidamente, pero hay millones de filas y casi todas van a tener un valor de Y. El ndice B* Tree resultante ser muy grande y el coste de su mantenimiento cuando actualicemos desde N a Y ser alto. Esta tabla parece ser una buena candidata para un ndice Bitmap (al fin y al cabo esto es baja cardinalidad), pero es un sistema transaccional y mucha gente insertar registros todo el tiempo, dejando la columna con valor N (como todos sabemos ste no sera un buen caso para un ndice de este tipo, ya que las operaciones se veran muy serializadas). As que lo que realmente queremos es indexar los registros que nos interesan (los registros N). Veremos como se puede hacer esto con ndices de tipo funcin, pero antes veremos que sucede si usamos un ndice normal, como si furamos programadores normalitos. Usando el siguiente script crearemos la tabla BIG_TABLE y actualizaremos su columna TEMPORARY intercambiando sus valores Y por N y viceversa: CREATE TABLE big_table TABLESPACE users AS SELECT ROWNUM id, a.* FROM all_objects a WHERE 1=0; ALTER TABLE big_table NOLOGGING; SET SCAN ON DEFINE ON DECLARE l_cnt NUMBER; l_rows NUMBER := &num_rows; BEGIN INSERT /*+ APPEND */ INTO big_table SELECT ROWNUM, a.* FROM all_objects a WHERE ROWNUM <= l_rows; l_cnt := sql%rowcount; COMMIT; WHILE (l_cnt < l_rows) LOOP INSERT /*+ APPEND */ INTO big_table SELECT ROWNUM+l_cnt, OWNER, OBJECT_NAME, SUBOBJECT_NAME, OBJECT_ID, DATA_OBJECT_ID, OBJECT_TYPE, CREATED, LAST_DDL_TIME, TIMESTAMP, STATUS, TEMPORARY, GENERATED, SECONDARY FROM big_table WHERE ROWNUM <= l_rows-l_cnt; l_cnt := l_cnt + SQL%ROWCOUNT;

COMMIT; END LOOP; END; / SELECT COUNT(*) FROM big_table; COUNT(*) ---------250000 UPDATE big_table SET temporary = DECODE(temporary,'N','Y','N'); COMMIT;

Y ahora comprobaremos el ratio de valores Y a N: SELECT temporary, cnt, ROUND((ratio_to_report(cnt) over ()) * 100, 2 ) rtr FROM (SELECT temporary, COUNT(*) cnt FROM big_table GROUP BY temporary); T CNT RTR - ---------- ---------N 55 ,02 Y 249945 99,98

Como se puede ver, del milln de filas en la tabla slo una mnima parte del 1% de los datos deben ser indexados. Si usamos un ndice convencional sobre la columna TEMPORARY (la cual est jugando el rol de PROCESSED_FLAG en el ejemplo) descubriremos que el ndice tiene 1.000.000 de entradas, consumiendo unos 14Mb de espacio y teniendo un peso de 3: CREATE INDEX processed_flag_idx ON big_table(temporary) TABLESPACE users; ANALYZE INDEX processed_flag_idx VALIDATE STRUCTURE; SELECT name, btree_space, lf_rows, height FROM index_stats; NAME BTREE_SPACE LF_ROWS HEIGHT ------------------------------ ----------- ---------- ---------PROCESSED_FLAG_IDX 3632032 250000 2

Cualquier recuperacin va este ndice incurrir en 2 operaciones I/ O para llegar a los bloques hoja. Este ndice no es slo ancho , sino que tambin es largo . Para obtener el primer registro no procesado tenemos que ralizar al menos 3 operaciones I/O (2 contra el ndice y una contra la tabla). Cmo podemos cambiar todo esto? Necesitamos hacer que el ndice sea mucho ms pequeo y fcil de mantener (lo cual generar mucha menos sobrecarga durante las operaciones UPDATE). Volviendo al tema de los ndices de tipo funcin, ellos nos permiten simplificar el proceso escribiendo una funcin que retorne NULL cuando no queramos indexar una fila y que retorne un valor no NULL cuando si queramos. Por ejemplo dado que estamos interesados slo en los registros N vamos a indexarlos: DROP INDEX processed_flag_idx; CREATE INDEX processed_flag_idx ON big_table(CASE temporary WHEN 'N' THEN 'N' END) TABLESPACE users; ANALYZE INDEX processed_flag_idx VALIDATE STRUCTURE; SELECT name, btree_space, lf_rows, height FROM index_stats;

NAME BTREE_SPACE LF_ROWS HEIGHT ------------------------------ ----------- ---------- ---------PROCESSED_FLAG_IDX 8000 55 1

Esta es una gran diferencia (el ndice es de 8Kb, no de 3,6Mb como antes). El peso tambin se ha reducido. Si usamos este ndice realizaremos menos operaciones I/O que usando el enorme ndice previo. Debo hacer notar que este ejemplo usa una tabla que no es de las grandes , ya que con slo 250.000 filas se podra considerar normalita , as que los beneficios de este mtodo sobre tablas realmente grandes son impensables. Un ltimo apunte que debo hacer y sobre el que debemos estar alerta es el de la reescritura de las funciones por parte de Oracle en este tipo de ndices. Aunque estemos creando un ndice segn cierta expresin Oracle puede detectar que no es eficiente y guardar una versin optimizada de la misma. Siempre que usemos este tipo de ndices de tipo funcin debemos comprobar lo que Oracle ha interpretado de nuestra funcin consultando de este modo: SELECT index_name, column_expression FROM USER_IND_EXPRESSIONS; INDEX_NAME COLUMN_EXPRESSION ------------------------------ ----------------------------------------PROCESSED_FLAG_IDX CASE "TEMPORARY" WHEN 'N' THEN 'N' END

para que nuestro predicado use la misma sintaxis y pueda as usar el ndice.

ndices del dominio de una aplicacin.Los ndices del dominio de una aplicacin son lo que Oracle denomina indexacin extensible. Ellos permiten crear vuestras propias estructuras de ndices que funcionarn como los ndices suministrados por Oracle. Cuando alguien lanza una sentencia CREATE INDEX usando uno de nuestros tipos, Oracle ejecutar nuestro cdigo para generar el ndice. Si alguien analiza el ndice o calcula sus estadsticas Oracle ejecutar nuestro cdigo para analizar o para generar estadsticas en la forma en que queramos. Cuando Oracle parsea una consulta y crea un plan de ejecucin nos preguntar como medir el coste para as evaluar diferentes planes. En pocas palabras, los ndices del dominio de aplicacin nos dan la habilidad de implementar un nuevo tipo de ndice que no existe en la base de datos todava. Por ejemplo si desarrollamos sofwtare que analiza imgenes almacenadas en la base de datos y produce informacin sobre ellas (como los colores que poseen), podemos crear nuestro propio ndice de imgenes. As como las imgenes son aadidas a la base de datos nuestro cdigo es invocado para extraer sus colores y almacenarlas de cualquier forma. Cuando se consulte, cuando un usuario busque imgenes "de color azul" Oracle preguntar a nuestro ndice cuando sea apropiado. El mejor ejemplo de esto es el ndice "Oracle Text". Este ndice es usado para proporcionar una palabra a la bsqueda en grandes textos.

Cundo se debe usar un ndice B* Tree ?No siendo un gran creyente en reglas de aplicacin (ya que hay excepciones a cada regla) no tengo ninguna serie de reglas que daros sobre cuando usar o no usar un ndice B* Tree . Para demostrar el porque de esto os presentar dos reglas que son igualmente vlidas: Slo usar un ndice B* Tree para indexar las columnas si vamos a acceder a un muy pequeo porcentaje de las filas en la tabla a travs del ndice. Usar un ndice B* Tree si vamos a procesar muchas filas de una tabla y el ndice puede ser usado en lugar de la tabla.

Estas reglas parecen dar consejos conflictivos, pero en realidad no, ya que slo cubren casos extremadamente diferentes. Hay dos modos de usar un ndice segn la precedencia del consejo: Como el medio de acceder a las filas en la tabla: Leeris el ndice para obtener una fila de la tabla. Aqu queris acceder a un porcentaje muy pequeo de filas en la tabla. Como el medio para responder a una pregunta: El ndice ya contiene suficiente informacin para responder por completo a la consulta, y por lo tanto no tenemos que acceder a la tabla. En este caso el ndice ser usado como una versin ms ligera de la tabla. Este es uno de mis mtodos favoritos.

Asimismo hay otros modos tambin, como por ejemplo usar un ndice para recuperar todas las filas de una tabla incluyendo las columnas que no estn en el ndice en si. Eso parece ir contra las dos reglas que he presentado. El caso en donde podra ser cierto sera en una aplicacin interactiva en donde se estn recuperando y mostrando algunas de las filas, despus algunas ms y as sucesivamente. En este caso queremos que la consulta est optimizada para el tiempo inicial de respuesta, no para la totalidad de los datos a procesar. El primer caso (usar el ndice si vamos a acceder a un porcentaje pequeo de las filas de la tabla) dice que si tenemos una tabla T y tenemos un plan de ejecucin como ste: SET AUTOTRACE TRACEONLY EXPLAIN SELECT owner, status FROM t WHERE owner = user; Execution Plan ---------------------------------------------------------0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=3 Card=1947 Bytes=25311) 1 0 TABLE ACCESS (BY INDEX ROWID) OF 'T' (TABLE) (Cost=3 Card=1947 .... 2 1 INDEX (RANGE SCAN) OF 'DESC_T_IDX' (INDEX) (Cost=2 Card=8)

entonces estaremos accediendo a un porcentaje muy pequeo de la tabla. El punto a destacar aqu es el INDEX (RANGE SCAN) seguido del TABLE ACCESS BY INDEX ROWID. Significa que Oracle leer el ndice y despus, para cada una de las entradas del ndice, realizar una lectura (operacin I/ O lgica o fsica) de un bloque de la base de datos para recuperar los datos. ste no es el mtodo ms eficiente si vamos a acceder a un porcentaje grande de las filas en la tabla T va el ndice. En el segundo caso (cuando el ndice puede ser usado en lugar de la tabla) podemos procesar el 100% (o cualquier porcentaje de hecho) de las filas desde el ndice. En vez de acceder al ndice y despus a la tabla nos quedamos ya con los datos del ndice, con lo que nos ahorramos muchsimas operaciones I/ O. Esta consulta demuestra este concepto: SELECT COUNT(*) FROM t WHERE owner = user; Execution Plan ---------------------------------------------------------0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=16 Card=1 Bytes=6) 1 0 SORT (AGGREGATE) 2 1 INDEX (RANGE SCAN) OF 'T_IDX' (INDEX) (Cost=16 Card=1947

aqu slo fue usado el ndice para contestar a la consulta, sin importar el porcentaje de filas al que estbamos accediendo. Podemos ver en el plan que la tabla nunca fue accedida, simplemente escaneamos la estructura del ndice en si. Es importante entender la diferencia entre estos dos conceptos. Cuando tenemos que realizar un TABLE ACCESS BY INDEX ROWID debemos asegurarnos de que slo accedemos a un porcentaje pequeo del total de bloques de la tabla, lo cual equivale a un porcentaje pequeo de filas, o que necesitamos que las primeras filas sean recuperadas lo ms rpido posible (el usuario las est esperando impacientemente). Si accedemos a un porcentaje muy alto de filas se tardar ms usando el ndice B* Tree que nicamente escaneando completamente la tabla. Con el segundo tipo de consulta, donde la respuesta es hallada completamente en el ndice, tenemos una historia diferente. Leemos un bloque del ndice y seleccionamos muchas de sus filas para procesar, despus vamos al siguiente bloque del ndice y seleccionamos ms, y as con todos (nunca vamos a la tabla). Existe un fast full scan que podemos realizar en los ndices para hacer que el proceso sea incluso ms rpido a veces. Un fast full scan se produce cuando la base de datos lee bloques de un ndice sin ningn orden en particular, simplemente empieza leyndolos. De esta forma las filas no son recuperadas de forma ordenada. Por lo general un ndice B* Tree debe ser aplicado a las columnas que son usadas frecuentemente en el predicado de una consulta y cuando esperamos que una fraccin pequea de los datos de la tabla sean recuperados o sino cuando el usuario final demande un retorno inmediato . En una tabla delgada (= tabla con pocas columnas o que son pequeas) esta fraccin puede ser muy pequea. Una consulta que use este ndice debera esperar recuperar un 2% un 3% (o incluso menos) de las filas totales de la tabla. En una tabla gorda (= tabla con muchas columnas o que son muy anchas) esta fraccin puede llegar hasta el 20% o el 25% de las filas de la tabla. Este consejo no siempre tiene sentido para todo el mundo de forma inmediata. Un ndice es almacenado de forma ordenada segn su clave de ndice. El ndice ser accedido de manera ordenada por esta clave. Los bloques de la tabla apuntados por el ndice estn almacenados de forma aleatoria (al estilo heap ). Por tanto as como leemos el ndice para acceder a la tabla, realizamos muchsimas lecturas I/ O aleatorias (= scattered ). Por scattered podemos entender que el ndice nos dice que debe leer el bloque 1, el bloque 1.000, el bloque 205, el bloque 321, el bloque 1, el bloque 1.032, el bloque 1, etc. (no est leyendo el bloque 1, el bloque 2, el bloque 3 de forma consecutiva). Esto provoca que se relean bloques muchas veces. La operaciones I/O de lectura de un nico bloque pueden ser muy lentas. Como un ejemplo ms simplstico de esto digamos que estamos leyendo desde una tabla delgada a travs de un ndice, y que recuperaremos el 20% de las filas. Asumamos que tenemos 100.000 filas en la tabla. El 20% es 20.000 filas. Si cada fila ocupa una media de 80 bytes, en una base de datos con un tamao de bloque de 8Kb se estarn almacenando unas 100 filas por bloque. Esto significa que la tabla tiene aproximadamente unos 1.000 bloques. A partir de ahora las matemticas son muy sencillas. Vamos a leer 20.000 filas usando el ndice, o lo que es lo mismo, realizaremos unas 20.000 operaciones TABLE ACCESS BY ROWID. Procesaremos 20.000 bloques de la tabla para ejecutar esta consulta. Sin embargo, slo hay 1.000 en toda la tabla! Acabaremos leyendo y procesando cada bloque de las tabla una media de 20 veces. Incluso si incrementramos el tamao de la fila hasta una magnitud de 800 bytes por fila y 10 filas por bloque, tendramos 10.000 bloques en la tabla. Los accesos con ndice para 20.000 filas provocaran que leyramos cada bloque una media de dos veces. En este caso un full table scan sera muchsimo ms eficiente que usar un ndice, ya que slo visitaramos cada bloque una vez. Cualquier consulta que usara este ndice para acceder a los datos no sera muy eficiente hasta que accediera a menos del 5% de los datos (para la columna de 800 bytes), entonces se estara accediendo a unos 5.000 bloques, e incluso menos para la columna de 80 bytes (el 0,5% o menos).

Compresin de ndices y tablas.Oracle soporta la compresin de ndices (desde Oracle8i) y de tablas (desde Oracle9i ver. 2). El algoritmo de compresin para ambos tipos de segmentos es similar y se basa en descartar informacin repetida y almacenarla una vez, en vez de guardar todas las apariciones. La compresin de ndices y tablas son levemente diferentes y pueden ser usadas en diferentes situaciones. Por ejemplo la compresin de ndices puede ser usada en cuyas tablas se estn produciendo modificaciones constantes, pero la compresin de tabla no tiene sentido en caso debido a su implementacin. Primero discutiremos la compresin de ndices y luego la compresin de tablas.

Usando la compresin de claves de ndice.La compresin de claves de ndices en Oracle nos permite eliminar los elementos repetidos en una clave de ndice y almacenar los valores iniciales slo una vez por bloque hoja en vez de una vez por fila por bloque hoja. Considerad la vista de la base de datos DBA_OBJECTS. Supongamos que copiamos esa tabla y creamos un ndice compuesto sobre las columnas OWNER, OBJECT_TYPE y OBJECT_NAME para poder recuperar rpidamente las filas que contienen un objeto especfico de un propietario. Normalmente el ndice almacenara valores como stos:

Usando la compresin de claves de ndices Oracle puede almacenar conceptualmente los valores as:

De hecho Oracle puede almacenar significativamente ms datos por bloque hoja que los que un ndice sin comprimir. Antes de revisar el ejemplo vamos a ver una serie de ventajas de los ndices comprimidos: consumen menos espacio en disco, por lo que proporcionan mayores ahorros de espacio; pueden reducir la cantidad de operaciones I/O fsicas que el sistema realiza; aumentan la eficacia del buffer cache. Simplemente hay menos bloques que cachear. Los bloques del ndice son cacheados de forma comprimida, de la misma forma en la que estn almacenados.

No obstante aqu hay algunas desventajas de los ndices comprimidos: ponen ms entradas por bloque, aumentando an ms la contencin en esas de por si compactadas estructuras. Si antes estbamos poniendo 200 filas por bloque hoja (200 filas posibles que la gente poda actualizar en sus sesiones concurrentes) ahora estamos poniendo 400; requieren ms tiempo de CPU para procesar la ejecucin porque la estructura es ms compleja. Podemos notar esto durante las operaciones INSERT y SELECT. Mantened presentes estas simples consideraciones. No significan que habr necesariamente ms contencin. Tampoco significan que al usar estos ndices se usar ms la CPU. Hay cosas que se deben vigilar y probar antes en caso de implementar esta caracterstica. Vamos a verlo en accin. Crearemos los ndices comprimidos y no comprimidos en una tabla y compararemos no slo su tamao, sino tambin su rendimiento. Empezaremos creando una copia de DBA_OBJECTS, la cual cuadruplicaremos en volumen. Eso significar que nuestro ndice sobre OWNER, OBJECT_TYPE, OBJECT_NAME apuntar a cuatro filas por clave aproximadamente: CREATE TABLE t1 TABLESPACE users AS SELECT * FROM dba_objects;

INSERT /*+ APPEND */ INTO t1 SELECT * FROM t1; 33777 filas creadas. COMMIT;

INSERT /*+ APPEND */ INTO t1 SELECT * FROM t1; 67554 filas creadas. COMMIT; CREATE INDEX uncompressed_idx ON t1(owner,object_type,object_name) TABLESPACE users; exec DBMS_STATS.GATHER_TABLE_STATS(user,'T1',null,100,cascade=>TRUE)

Ahora, dado que no podemos crear otro ndice en las mismas columnas de esta tabla, copiaremos la tabla otra vez e indexaremos los mismos datos, usando esta vez la compresin de claves de ndices sobre los tres valores: CREATE TABLE t2 TABLESPACE users AS SELECT * FROM t1; CREATE INDEX compressed_idx ON t2(owner,object_type,object_name) TABLESPACE users COMPRESS 3; exec DBMS_STATS.GATHER_TABLE_STATS(user,'T2',null,100,cascade=>TRUE)

Estamos listos para medir las diferencias de espacio entre estos dos ndices. Para ello usaremos la vista dinmica INDEX_STATS. Esta vista se llena con los detalles sobre la estructura de un ndice despus del comando ANALYZE INDEX ... VALIDATE STRUCTURE. Esta vista como mucho contendr una fila cada vez, as que copiaremos esa fila mientras validemos los ndices: ANALYZE INDEX uncompressed_idx VALIDATE STRUCTURE; CREATE TABLE index_stats_copy TABLESPACE users AS SELECT * FROM index_stats; ANALYZE INDEX compressed_idx VALIDATE STRUCTURE; INSERT INTO index_stats_copy SELECT * FROM index_stats; 1 fila creada.

Ahora estamos listos para compararlos. Para una comparacin frente a frente usaremos este SQL: SELECT 'HEIGHT', MAX(DECODE(name,'UNCOMPRESSED_IDX',HEIGHT,null)), MAX(DECODE(name,'UNCOMPRESSED_IDX',to_number(null),HEIGHT)) FROM index_stats_copy UNION ALL SELECT 'BLOCKS', MAX(DECODE(name,'UNCOMPRESSED_IDX',BLOCKS,null)), MAX(DECODE(name,'UNCOMPRESSED_IDX',to_number(null),BLOCKS)) FROM index_stats_copy UNION ALL ...(query for each column)...

Pero si no os gusta tanto teclear ni como sale tambin se puede probar con este bloque PL/SQL: variable x refcursor

DECLARE l_stmt LONG; BEGIN FOR x IN (SELECT ''''||column_name||''''quoted,column_name FROM user_tab_columns WHERE table_name = 'INDEX_STATS_COPY' AND column_name not in ('NAME','PARTITION_NAME') ) LOOP l_stmt := l_stmt || ' select ' || x.quoted || ' name, max(decode(name,''UNCOMPRESSED_IDX'',' ||x.column_name || ',null)) uncompressed, max(decode(name,''UNCOMPRESSED_IDX'',to_number(null),' || x.column_name ||')) compressed FROM index_stats_copy union all'; END LOOP; l_stmt := 'SELECT name, uncompressed, compressed, uncompressed-compressed diff, decode(uncompressed,0, to_number(null), ROUND(compressed/uncompressed*100,2)) pct FROM ('||SUBSTR(l_stmt,1,LENGTH(l_stmt)-LENGTH(' union all'))||') order by name'; open :x for l_stmt; END; / print x NAME UNCOMPRESSED COMPRESSED DIFF PCT -------------------- ------------ ---------- ---------- ---------BLKS_GETS_PER_ACCESS 5,50981792 5,50981792 0 100 BLOCKS 1024 512 512 50 BR_BLK_LEN 8032 8032 0 100 BR_BLKS 6 3 3 50 BR_ROWS 908 411 497 45,26 BR_ROWS_LEN 40250 15495 24755 38,5 BTREE_SPACE 7320192 3318448 4001744 45,33 DEL_LF_ROWS 0 0 0 DEL_LF_ROWS_LEN 0 0 0 DISTINCT_KEYS 33612 33612 0 100 HEIGHT 3 3 0 100 LF_BLK_LEN 8000 7996 4 99,95 LF_BLKS 909 412 497 45,32 LF_ROWS 135108 135108 0 100 LF_ROWS_LEN 6504444 1486188 5018256 22,85 MOST_REPEATED_KEY 64 64 0 100 OPT_CMPR_COUNT 3 3 0 100 OPT_CMPR_PCTSAVE 54 0 54 0 PCT_USED 90 90 0 100 PRE_ROWS 0 33768 -33768 PRE_ROWS_LEN 0 1457953 -1457953 ROWS_PER_KEY 4,01963584 4,01963584 0 100 USED_SPACE 6544694 2959636 3585058 45,22 23 filas seleccionadas.

Ahora tenemos una comprobacin frente a frente del ndice sin comprimir y del ndice comprimido. Primero observamos que tenemos un ratio de compresin superior, con un 50%. Ignorad el valor de BLOCKS durante un momento (este espacio es asignado al segmento pero incluye tambin los bloques sin usar). Mirando a BR_BLKS (bloques rama) y LF_BLKS (bloques hoja, donde la claves actuales del ndice y los ROWID son almacenados), vemos que tenemos el 50% de bloques de los bloques rama y un 45% de bloques hoja. El ndice comprimido est usando casi la mitad de espacio en disco. Otra manera de ver esto es que cada bloque en el ndice

comprimido el doble de datos. No slo ahorramos espacio en disco, sino que adems hemos doblado el tamao del buffer cache. Tambin debemos considerar que Oracle lee bloques, no filas, por eso cualquier cosa que reduzca el nmero de bloques que Oracle debe manejar potencialmente aumentar la capacidad del buffer cache. Otra interesante cosa a notar aqu es el OPT_CMPR_COUNT (longitud ptima de compresin de clave) y el OPT_CMPR_PCTSAVE (% de ahorro si se comprime) valores. Los resultados indican que si hubiramos usado la compresin de claves sobre el ndice sin comprimir podramos habernos ahorrado un 54% de espacio (un valor que es exacto como se muestra en el ndice comprimido). Podis usar estos valores para ver si reconstruyendo un ndice con compresin ser recompensado con ahorros de espacio significativos. El ltimo de los tems que consideraremos aqu son PRE_ROWS (filas prefijo, o valores actuales de clave) y PRE_ROWS_LEN (longitud de esos prefijos de fila). Estos nmeros representan el nmero de prefijos nicos de clave en el ndice. Representan los datos que han sido eliminados de la estructura del ndice y almacenados una vez por bloque hoja en vez de una vez por cada fila. Aqu tenemos una valor de 33.768. Esto no es sorprendente ya que el nmero de filas en mi tabla DBA_OBJECTS era de 33.777 (podis ver que con el primer INSERT / * + APPEND* / , la primera "doblez" de la tabla). Eso est mostrando que aparentemente hay 33.768 combinaciones nicas de (OWNER, OBJECT_TYPE, OBJECT_NAME) en la tabla. El valor PRE_ROWS_LEN muestra que esos valores eliminados ocupaban 1,4Mb de espacio. Para los nmeros "sin formato" podemos ejecutar un simple test que toma cada clave y lee la fila entera usando esa clave. Recordad que cada valor de clave en el ndice est cuatro veces en el ndice, por lo que estamos recuperando cada valor de clave cuatro veces para 16 filas. ALTER SESSION SET sql_trace=TRUE; BEGIN FOR x IN ( SELECT * FROM t1 ) LOOP FOR y IN ( SELECT * FROM t1 WHERE owner AND object_name = x.object_name AND object_type = x.object_type ) NULL; END LOOP; END LOOP; FOR x IN ( SELECT * FROM t2 ) LOOP FOR y IN ( SELECT * FROM t2 WHERE owner AND object_name = x.object_name AND object_type = x.object_type ) NULL; END LOOP; END LOOP; END; / ALTER SESSION SET sql_trace=FALSE; = x.owner LOOP

= x.owner LOOP

El informe TKPROF para esta ejecucin muestra las siguientes estadsticas: SELECT * FROM T1 WHERE OWNER = :B3 AND OBJECT_NAME = :B2 AND OBJECT_TYPE = :B1 call ------Parse Execute Fetch ------total Rows ------572688 count -----1 135108 707796 -----842905 cpu elapsed disk query current -------- ---------- ---------- ---------- ---------0.00 0.00 0 0 0 9.87 9.34 0 0 0 33.75 2115.08 6801 1550696 0 -------- ---------- ---------- ---------- ---------43.62 2124.43 6801 1550696 0 rows ---------0 0 572688 ---------572688

Row Source Operation --------------------------------------------------TABLE ACCESS BY INDEX ROWID T1 (cr=1550696 r=6801 w=0 time=20572297 us)

572688 INDEX RANGE SCAN UNCOMPRESSED_IDX (cr=978008 r=1444 w=0 time=11936308 us)(object id 41033) ******************************************************************************** SELECT * FROM T2 WHERE OWNER = :B3 AND OBJECT_NAME = :B2 AND OBJECT_TYPE = :B1 call ------Parse Execute Fetch ------total Rows ------572688 572688 count -----1 135108 707796 -----842905 cpu elapsed disk query current -------- ---------- ---------- ---------- ---------0.00 0.00 0 0 0 9.26 9.38 0 0 0 36.68 34.50 5912 1550696 0 -------- ---------- ---------- ---------- ---------45.95 43.88 5912 1550696 0 rows ---------0 0 572688 ---------572688

Row Source Operation --------------------------------------------------TABLE ACCESS BY INDEX ROWID T2 (cr=1550696 r=5912 w=0 time=2240965697 us) INDEX RANGE SCAN COMPRESSED_IDX (cr=978008 r=592 w=0 time=14684646 us)(obj

Como podis ver los tiempos de CPU son ms o menos los mismos (s, varan en 2 segundos de CPU, pero eso slo representa una variacin del 5%, as que podemos decir que son casi equivalentes). Si repitiramos la ejecucin otra vez los nmeros podran ser invertidos. Esto es debido al hecho que 33,75 segundos de CPU reportados como por la fase de "fetch" para la primera consulta es una agregacin de 707.796 eventos discretos. Si alguno de los eventos tard menos tiempo para ejecutarse que la granularidad del reloj de sistema, se report 0; los otros pueden retornar 1 unidad de tiempo. (De hecho realiz un par de ejecuciones para comprobar que es reproducible y en una ocasin observ tiempos de CPU de 60,06 y 60,97 para ndices sin comprimir versus ndices comprimidos). El informe TKPROF tabin muestra que la cantidad de operaciones I/ O lgicas por cada SQL ha sido la misma. Esto no es sorprendente dado que el "peso" de ambos ndices es el mismo (como se observ por los valores de INDEX_STATS precedentes). Incluso ms, dada cualquier clave de entrada, realizara la misma cantidad de operaciones I/ O lgicas para obtener la entrada hoja. En este caso realizara de media dos I/ O lgicas para obtener el boque hoja y uno ms para recuperar los datos de la tabla en si. Respecto al tiempo slo dir que el ndice sin comprimir fue slo un 3% ms rpido que el comprimido, algo que es casi imperceptible. Aunque la diferencia est en las operaciones I/O fsicas, que varan en 889 unidades.

Usando la compresin en tablas de slo lectura o mayoritariamente de lecturas.La habilidad para comprimir una tabla se inici en Oracle8i con las tablas IOT. Cualquiera puede deducir fcilmente que el mecanismo de compresin es el mismo que el del apartado anterior: comprimiendo el ndice. Autntica compresin a nivel de tabla para tablas ms generales vino con Oracle9i ver.2. Es similar a la compresin de claves de ndices ya que tambin elimina la informacin repetida, pero es diferente en varios aspectos. Cuando Oracle construye un bloque de la base datos busca y elimina los valores que se repiten en todas las columnas y filas. La principal diferencia entre la compresin de tablas y la de ndices es cuando puede ser usada cada una. La compresin de claves de ndices trabaja igualmente bien en sistemas en donde la tabla est siendo modificada frecuentemente y en sistemas de slo-lectura o mayoritariamente lecturas. El ndice es mantenido en su estado comprimido en ambos entornos. Sin embargo la compresin de tablas trabaja bien cuando se usa en un entorno de slo-lectura o de mayoritariamente lecturas. No es una caracterstica que se pueda usar en tablas de transacciones. La compresin de tablas tambin funciona bien con operaciones tipo "bulk". Un INSERT o UPDATE normales no comprimirn los datos, as que en su lugar necesitaris usar uno de los siguientes tipos de sentencias: CREATE TABLE AS INSERT INSERT /*+ APPEND */ (insercin "direct-path") SQLLDR direct=y (carga "direct-path") ALTER TABLE MOVE

Comprimir una tabla no prohibe usar sentencias normales DML contra ella. Slo que los datos aadidos o la informacin modificada no ser almacenada de forma comprimida. Ser necesario reconstruir ese segmento para comprimir los datos. As que la compresin de tablas ser ms valiosa en estas situaciones: Grandes cantidades de informacin de referencias estticas que son de slo lectura o que mayoritariamente slo son ledas. Entornos data warehouse en donde las operaciones tipo bulk son comunes. Informacin de auditora almacenada en tablas particionadas, donde por ejemplo se puede comprimir la informacin de auditora del mes pasado al comienzo del nuevo mes.

La compresin de tablas no debe ser considerada para tablas transaccionales donde los datos son actualizados contnuamente. En este caso la compresin de la tabla ser desactivada y cada UPDATE tender a descomprimir la fila procesada actualmente. La compresin de tablas en Oracle es obtenida eliminando los datos repetidos en un bloque de base de datos y creando una tabla de smbolos. Un bloque normal es parecido a este (cada celda en la tabla representa una fila):

Un bloque comprimido conceptualmente se parecera a esto:

Aqu los valores repetidos de SCOTT, TABLE, INDEX y PROCEDURE han sido eliminados, ya que han sido almacenados slo una vez en la tabla de smbolos del bloque, y reemplazados a su vez en el bloque por simples ndices. De aqu viene el ahorro de espacio. Considerad este ejemplo en donde almacenamos DBA_OBJECTS en una tabla sin comprimir y en otra comprimida. Notad que debido a que el PCTFREE de la tabla comprimida ser 0 lo declaro en la creacin de la tabla sin comprimir: CREATE TABLE uncompressed PCTFREE 0 TABLESPACE users AS SELECT * FROM dba_objects ORDER BY owner, object_type, object_name; exec dbms_stats.gather_table_stats (user,'UNCOMPRESSED')

CREATE TABLE compressed TABLESPACE users COMPRESS AS SELECT * FROM uncompressed ORDER BY owner, object_type, object_name;

exec dbms_stats.gather_table_stats (user,'COMPRESSED') SELECT cblks comp_blks, uncblks uncomp_blks, ROUND(cblks/uncblks*100,2) pct FROM ( SELECT MAX(DECODE(table_name,'COMPRESSED',blocks,null)) cblks, MAX(DECODE(table_name,'UNCOMPRESSED',blocks,null)) uncblks FROM user_tables WHERE table_name IN ( 'COMPRESSED', 'UNCOMPRESSED' )); COMP_BLKS UNCOMP_BLKS PCT ---------- ----------- ---------247 428 57,71

Esto muestra que podemos almacenar la misma cantidad de informacin en el 57% del espacio. Esto es importante (podemos reducir por la mitad nuestras necesidades de espacio en disco para esta tabla). Notad tambin que us ORDER BY en la sentencia CREATE TABLE COMPRESSED y orden la tabla por los datos que yo saba que eran ms comprensibles. De esta forma agrup juntos los objetos del mismo tipo en los mismos bloques para el usuario SCOTT. Esto permite que las rutinas de compresin hagn mejor su trabajo. En vez de almacenar docenas de miles de veces SCOTT (o SYSTEM, CTXSYS o el que sea) slo los almacen una vez. Tambin slo almacen TABLE y PACKAGE BODY una vez, creando punteros extremadamente pequeos. Podis pensar que esto es un truco: en realidad no datos no estarn ordenados. Debo disentir de eso. La compresin de tablas es til slo en datos de slo-lectura o de mayoritariamente lecturas. Tenis un gran control sobre la ordenacin de estos datos. Cargndola en un "warehouse", teniendo una cola de auditora o teniendo informacin sobre referencias, en todos estos casos los datos puede ser ordenados. Si vuestro objetivo es minimizar el uso de disco podis conseguirlo. Para entretenernos un poco vamos a ver que pasa si los datos quedan con una ordenacin aleatoria: DROP TABLE compressed; CREATE TABLE compressed TABLESPACE users COMPRESS AS SELECT * FROM uncompressed ORDER BY dbms_random.random; exec dbms_stats.gather_table_stats (user,'COMPRESSED') SELECT cblks comp_blks, uncblks uncomp_blks, ROUND(cblks/uncblks*100,2) pct FROM ( SELECT MAX(DECODE(table_name,'COMPRESSED',blocks,null)) cblks, MAX(DECODE(table_name,'UNCOMPRESSED',blocks,null)) uncblks FROM user_tables WHERE table_name IN ( 'COMPRESSED', 'UNCOMPRESSED' )); COMP_BLKS UNCOMP_BLKS PCT ---------- ----------- ---------323 428 75,47

Incluso si los datos en esta tabla particular llegan de forma aleatoria podemos conseguir una reduccin del espacio necesitado en un 25%, aunque esto puede variar. He visto nmeros entre el 65% y el 80% durante ejecuciones repetidas. Usando las estadsticas presentes en la tabla USER_TAB_COLUMNS para la tabla UNCOMPRESSED not que la columna TIMESTAMP era particularmente ancha y tena un montn de valores repetidos y no NULL: exec dbms_stats.gather_table_stats (user,'UNCOMPRESSED') SELECT column_name, num_distinct, num_nulls, avg_col_len FROM user_tab_columns WHERE table_name = 'UNCOMPRESSED';

COLUMN_NAME NUM_DISTINCT NUM_NULLS AVG_COL_LEN -------------------------------- ------------ ---------- ----------OWNER 32 0 6 OBJECT_NAME 21525 0 24 SUBOBJECT_NAME 36 33556 2 OBJECT_ID 33781 1 5 DATA_OBJECT_ID 4440 29306 2 OBJECT_TYPE 34 0 9 CREATED 3223 0 8 LAST_DDL_TIME 3260 1 8 TIMESTAMP 3404 1 20 STATUS 2 0 7 TEMPORARY 2 0 2 GENERATED 2 0 2 SECONDARY 1 0 2 13 filas seleccionadas.

Reconstru la tabla comprimida ordenando esta vez por TIMESTAMP y los resultados fueron impresionantes: DROP TABLE compressed; CREATE TABLE compressed TABLESPACE users COMPRESS AS SELECT * FROM uncompressed ORDER BY timestamp; exec dbms_stats.gather_table_stats (user,'COMPRESSED') SELECT cblks comp_blks, uncblks uncomp_blks, ROUND(cblks/uncblks*100,2) pct FROM ( SELECT MAX(DECODE(table_name,'COMPRESSED',blocks,null)) cblks, MAX(DECODE(table_name,'UNCOMPRESSED',blocks,null)) uncblks FROM user_tables WHERE table_name IN ( 'COMPRESSED', 'UNCOMPRESSED' )); COMP_BLKS UNCOMP_BLKS PCT ---------- ----------- ---------180 428 42,06

Esa tabla ahora ocupa casi una tercera parte del espacio de la versin sin comprimir debido a que se ha eliminado la columna ancha y la ha almacenado slo una vez por bloque. Esto muestra que un buen entendimiento de la frecuencia de valores en vuestros datos, el tamao de nuestros datos y la habilidad para ordenarlos permitir conseguir el mximo de compresin si ese es nuestro objetivo. Los ejemplos en esta seccin usan diferentes opciones de almacenamiento para conseguir diferentes objetivos y algunas veces esos objetivos son ortogonales uno respecto a otro. Por ejemplo podis pensar que ordenando por TIMESTAMP se consigue el mximo de compresin pero tambin podis observar que la gente consulta con frecuencia todas las tablas de un usuario en concreto. En ese caso, ordenando por OWNER, OBJECT_TYPE colocar los datos que esa gente pide en sus consultas en el menor nmero posible de bloques (todos los datos de las tablas del usuario SCOTT tendern a quedar recogidas en un pequeo nmero de tablas). As que aqu se debe hacer una eleccin: queris usar el menor espacio de disco posible pero repartir potencialmente los objetos de SCOTT entre muchos bloques? o queris usar un poco ms de espacio en disco pero teniendo todas las tablas de SCOTT en los mismos bloques? No hay una nica solucin correcta. Slo hay la contestacin que sea til despus de tomar en consideracin nuestros objetivos (qu preferimos que pase en nuestro sistema?). A continuacin vamos a estudiar los tres casos en los que las tablas comprimidas son recomendables. Tablas comprimidas para informaciones estticas y "data warehouse" Si tenemos grandes cantidades de informaciones estticas que ya estn cargadas en nuestra base de datos podemos usar la compresin de tablas as:

analizando la tabla para encontrar la mejor ordenacin de columnas y as conseguir la mxima compresin (asumiendo que ese es nuestro objetivo); usando CREATE TABLE COPY_OF_TABLE compress AS SELECT con el requisito ORDER BY; borrar la tabla vieja sin comprimir y renombrar esta copia Si estamos en una situacin "data warehouse" y estamos cargando datos mediante procesos "bulk" podemos seguir el mismo procedimiento bsico. Por ejemplo podemos usar DBMS_STATS contra tablas externas. Si no estamos seguros de las frecuencias en los datos que estamos cargando podemos usar la misma aproximacin para nuestras tablas de base de datos. Usando la tabla anterior BIG_TABLE podemos hacer lo siguiente: ALTER TABLE big_table ADD CONSTRAINT big_table_pk PRIMARY KEY(id); BEGIN dbms_stats.gather_table_stats ( ownname => user, tabname => 'BIG_TABLE', -- estimate_percent => 30, method_opt => 'FOR ALL COLUMNS', cascade => TRUE ); END; / SELECT column_name, num_distinct, num_nulls, avg_col_len FROM user_tab_columns WHERE table_name = 'BIG_TABLE'; COLUMN_NAME NUM_DISTINCT NUM_NULLS AVG_COL_LEN -------------------------------- ------------ ---------- ----------ID 499723 0 5 OWNER 15 0 6 OBJECT_NAME 14737 0 25 SUBOBJECT_NAME 0 499070 2 OBJECT_ID 26027 0 5 DATA_OBJECT_ID 0 498403 2 OBJECT_TYPE 16 0 9 CREATED 2138 0 8 LAST_DDL_TIME 2034 0 8 TIMESTAMP 2191 0 20 STATUS 2 0 7 TEMPORARY 2 0 2 GENERATED 2 0 2 SECONDARY 1 0 2 14 filas seleccionadas. A partir de eso podemos deducir que la mxima compresin ser obtenida ordenando por OBJECT_NAME. Debido a que tiene relativamente pocos valores distintos en comparacin al nmero de filas en la tabla y a que la longitud media de fila es muy grande ser la mejor candidata. Podemos carga la tabla usando este cdigo: CREATE TABLE big_table_compressed TABLESPACE users COMPRESS AS SELECT * FROM big_table ORDER BY object_name;

Cuando he comparado los tamaos de la versin comprimida versus la versin sin comprimir de los mismos datos he encontrado que la tabla comprimida us 4.831 bloques y la versin sin comprimir us 22.1888 bloques (un ratio de compresin de 5 a 1). Comprimiendo historiales de auditora o de transacciones Supongamos por ejemplo que tenemos una ventana deslizante de datos en una tabla particionada. Estamos manteniendo siete aos de informacin de auditoras en lnea en nuestra base de datos. Cada mes tomamos los

datos del mes ms antigo y lo borramos, aadiendo una nueva particin a la tabla para acomodar los nuevos datos y ahora queremos tambin comprimir los datos del ltimo mes. Echaremos un vistazo a como podemos conseguir esto paso a paso. Empezaremos con nuestra tabla particionada AUDIT_TRAIL_TABLE: CREATE TABLE audit_trail_table ( timestamp date, username varchar2(30), action varchar2(30), object varchar2(30), message varchar2(80) ) TABLESPACE users PARTITION BY RANGE (timestamp) ( PARTITION jan_2002 VALUES LESS THAN (TO_DATE('01-feb-2002','dd-mon-yyyy')) , PARTITION feb_2002 VALUES LESS THAN (TO_DATE('01-mar-2002','dd-mon-yyyy')) , PARTITION mar_2002 VALUES LESS THAN (TO_DATE('01-apr-2002','dd-mon-yyyy')) , PARTITION apr_2002 VALUES LESS THAN (TO_DATE('01-may-2002','dd-mon-yyyy')) , PARTITION mar_2003 VALUES LESS THAN (TO_DATE('01-apr-2003','dd-mon-yyyy')) , PARTITION the_rest VALUES LESS THAN (MAXVALUE) ); CREATE INDEX partitioned_idx_local ON audit_trail_table(username) TABLESPACE users LOCAL;

Esta tabla tiene una particin para los datos de cada mes, desde enero de 2002 hasta el final de marzo de 2003. Ahora asumiremos que estamos a principios de abril y que tenemos un mes de informacin de auditora sin comprimir en la particin MAR_2003. Ahora necesitamos hacer lo siguiente: deshacernos de los datos ms antigos (= desplazar la ventana de datos hacia la derecha) aadir una nueva particin slo para abril de 2003 comprimir los datos de marzo de 2003 ya que no ser necesario aadirles nada ms (slo consultas)

Primero generaremos algunos datos de pruebas para marzo de 2003: INSERT INTO audit_trail_table SELECT TO_DATE('01-mar-2003 '|| TO_CHAR(created,'HH24:MI:SS'),'DD-MON-YYYY HH24:MI:SS') + MOD(ROWNUM,31),owner, DECODE(MOD(ROWNUM,10),0,'INSERT',1,'UPDATE',2,'DELETE', 'SELECT'), object_name, object_name || ' ' || dbms_random.random FROM (SELECT * FROM dba_objects UNION ALL SELECT * FROM dba_objects UNION ALL SELECT * FROM dba_objects UNION ALL SELECT * FROM dba_objects); 135188 rows created. exec dbms_stats.gather_table_stats (user,'AUDIT_TRAIL_TABLE',partname=>'MAR_2003',granularity=>'PARTITION') SELECT num_rows, blocks FROM user_tab_partitions WHERE table_name = 'AUDIT_TRAIL_TABLE' AND partition_name = 'MAR_2003'; NUM_ROWS BLOCKS ---------- ---------135188 1566

Ahora usaremos las estadsticas de columna para determinar como podemos ordenar estos datos para conseguir nuestro objetivo de la mxima compresin: SELECT column_name, num_distinct, num_nulls, avg_col_len FROM user_part_col_statistics WHERE table_name = 'AUDIT_TRAIL_TABLE' AND partition_name = 'MAR_2003'; COLUMN_NAME NUM_DISTINCT NUM_NULLS AVG_COL_LEN -------------------------------- ------------ ---------- ----------TIMESTAMP 59717 0 8 USERNAME 32 0 6 ACTION 4 0 7 OBJECT 21528 0 24 MESSAGE 135188 0 35

Podemos ver que la columna MESSAGE es nica pero OBJECT no es muy selectiva y es grande. As que decidimos ordenar por OBJECT y despus por USERNAME/ ACTION segn estas informaciones. Crearemos una tabla TEMP con los datos ordenados y comprimidos: CREATE TABLE temp TABLESPACE users COMPRESS AS SELECT timestamp, username, action, object, message FROM audit_trail_table PARTITION(mar_2003) ORDER BY object, username, action;

E indexaremos la tabla para que estructuralmente se parezca ms a una tabla particionada: CREATE INDEX temp_idx ON temp(username) TABLESPACE users; BEGIN dbms_stats.gather_table_stats ( ownname => user, tabname => 'TEMP', method_opt => 'FOR ALL INDEXED COLUMNS', cascade => TRUE ); END; /

Ahora ya estamos en disposicin de mover los datos. Empezaremos borrando la particin ms antiga y desplazando la ventana hacia la derecha: ALTER TABLE audit_trail_table DROP PARTITION jan_2002;

Despus intercambiaremos nuestra ventana de datos compactada usando el comando EXCHANGE PARTITION: SET TIMING ON ALTER TABLE audit_trail_table EXCHANGE PARTITION mar_2003 WITH TABLE temp INCLUDING INDEXES WITHOUT VALIDATION; Table altered. Elapsed: 00:00:00.00 SET TIMING OFF

Notad que esta operacin se realiza de forma instantnea. Es una simple sentencia DDL que intercambia nombres, los datos no son tocados durante esta operacin. Adems podemos ver que las estadsticas tambin han sido intercambiadas: SELECT blocks FROM user_tab_partitions WHERE table_name = 'AUDIT_TRAIL_TABLE' AND partition_name = 'MAR_2003'; BLOCKS ---------959

En vez de 1.566 bloques ahora tenemos 959, por lo que los datos estn ocupando casi el 61% del espacio que ocupaban originalmente. Todo lo que queda ahora es dividir la ltima particin en una nueva particin para abril y para el resto de datos: ALTER TABLE audit_trail_table SPLIT PARTITION the_rest AT (TO_DATE('01-may-2003','DD-MON-YYYY')) INTO (PARTITION apr_2003,PARTITION the_rest);

Ahora ya hemos acabado. Es interesante destacar que los datos de marzo todava son modificables aqu. Podemos insertar, actualizar y borrar esos datos. Sin embargo cualquier fila que insertemos usando un INSERT convencional o que modifiquemos con un UPDATE, causar que los datos afectados estn sin comprimir. Una tabla puede estar compuesta de bloques comprimidos y de bloques sin comprimir. De hecho incluso un nico bloque puede contener filas comprimidas y sin comprimir.

Paralelismo.La ejecucin en paralelo es una caracterstica de la versin Enterprise (no est disponible en la Estndard) fue introducida en Oracle versin 7.1.6. Es la habilidad de romper fsicamente una tarea grande y serializada (cualquier DML o DDL) en trozos ms pequeos que pueden ser procesados simultneamente. Las ejecuciones paralelas en Oracle imitan los procesos de la vida real que vemos todo el tiempo. Raramente veremos a un nico individuo construir una casa, es ms probable que varios equipos de personas trabajen de forma concurrente para construir rpidamente la casa. De ese modo ciertas tareas pueden ser divididas en tareas ms pequeas y hechas al mismo tiempo. Por ejemplo, la fontanera y el cableado elctrico se pueden ejecutar al mismo tiempo para reducir el tiempo total requerido para completar la casa. La ejecucin paralela en Oracle sigue la misma lgica. Con frecuencia es posible para Oracle el dividir un cierto gran trabajo en partes ms pequeas y realizar cada una de ellas concurrentemente. Si es necesitado un full table scan por ejemplo no hay ninguna razn por la que Oracle no pueda tener cuatro sesiones paralelas, llamadas P001 a P004, realizando el full scan juntas, con cada sesin leyendo una porcin diferente de la tabla. Si los datos escaneados por P001-P004 necesitan ser ordenados, esto puede ser hecho por otras cuatro sesiones paralelas, llamadas P005-P008, las cuales enviarn los resultados de la consulta a una sesin coordinadora. La ejecucin paralela es una herramienta que si es usada adecuadamente puede incrementar el tiempo de respuesta de ciertas operaciones en varios rdenes de magnitud. Cuando se usa como un conmutador RPIDO= TRUE los resultados normalmente son opuestos. En este captulo el objetivo no es explicar detalladamente (al igual que en todo este cursillo) como las consultas paralelas estn implementadas en Oracle: hay una mirada de planes que pueden ejecutarse en paralelo. El objetivo de este captulo ser proporcionarnos un entendimiento sobre para que tipos de problemas la ejecucin en paralelo es adecuada y para cuales no. Ms especficamente trataremos lo siguiente: Consultas en paralelo: La habilidad de realizar una simple consulta usando muchos procesos o threads del sistema operativo. Oracle hallar que operaciones pueden ser realizadas en paralelo, como los full table scans o las grandes ordenaciones, y crear un plan de ejecucin para hacer eso. DML en paralelo (o PDML): Esto es muy similar en a la consulta en paralelo pero es usado para realizar operaciones de modificacin de datos (INSERT, UPDATE, DELETE o MERGE) usando el procesamiento en paralelo. DDL en paralelo: Las operaciones DDL en paralelo son sencillamente la capacidad de Oracle para realizar grandes operaciones DDL en paralelo. Seran ejemplos la reconstruccin de un ndice, la creacin de un nuevo ndice, una carga de datos y la reorganizacin de grandes tablas. Creo que es ste es el lugar adecuado para el paralelismo en la base de datos y por eso lo trataremos en profundidad. Recuperacin en paralelo: Es la habilidad de la base de datos de realizar recuperacin de instancia o recuperacin del medio fsico en paralelo para reducir el tiempo necesario para que la base de datos est lista. Paralelismo procedimental: Es la habilidad para ejecutar en paralelo un cdigo desarrollado. En este captulo discutiremos dos aproximaciones a esto. En la primera aproximacin Oracle ejecutar cdigo PL/ SQL de programador en paralelo de una forma invisible al programador (programador que no est desarrollando cdigo en paralelo). La segunda aproximacin es algo que denomino paralelismo casero , en donde el cdigo desarrollado est diseado especficamente para ser ejecutado en paralelo.

Cundo usar la ejecucin en paralelo?


La ejecucin en paralelo puede ser fantstica. Permite tomar un proceso que se ejecuta durante varias horas o das y completarlo en minutos. Dividiendo un problema enorme en pequeos componentes podemos, en algunos casos, reducir dramticamente el tiempo total de procesamiento. Sin embargo un concepto fundamental ser til recordar cuando consideremos su uso: Las sentencias en paralelo son esencialmente no escalables. La ejecucin en paralelo fue diseada para permitir a un usuario individual o a una sentencia SQL particular consumir todos los recursos de la base de datos. Si tenemos una caracterstica que permite a un individuo hacer uso de todo lo que est disponible, y tenemos a dos individuos que usan esta caracterstica, obviamente se producirn problemas de contencin. Si tenemos por ejemplo un servidor con cuatro CPU y una media de 32 usuarios ejecutando consultas simultneamente, significa que no queremos paralelizar todas sus operaciones. Si

hubiramos permitido a cada usuario realizar una consulta parallel 2 tendramos 64 operaciones concurrentes ejecutndose en un servidor con slo cuatro CPU. Si el servidor no estuviera sobrecargado antes de estas operaciones seguramente al ejecutarlas s lo estara. Por lo pronto la ejecucin en paralelo puede ser una idea terrible. En muchos casos la aplicacin del procesamiento en paralelo slo incrementar el consumo de recursos, ya que se basa en el uso de todos los recursos disponibles. En un sistema en donde los recursos deben ser compartidos entre muchas transacciones concurrente, como en un sistema OLTP, se observarn tiempos de respuesta aumentados debido a esto. Oracle evita ciertas tcnicas de ejecucin que pueden ser usadas eficientemente en un planes de ejecucin en serie, y adopta rutas de ejecucin, como los full scans , en la creencia de que la divisin del proceso en partes pequeas ser mejor que el plan de ejecucin en serie. La ejecucin en paralelo, cuando es aplicada de forma inapropiada, puede ser la causa de un problema de rendimiento, no una solucin para l. As que antes de aplicar la ejecucin en paralelo necesitamos que dos cosas sean ciertas: Tenemos que realizar una tarea verdaderamente pesada, como el full scan de 50Gb de datos. Debemos tener suficientes recursos disponibles. Antes de escanear completamente 50Gb de datos debemos estar seguros de que hay suficiente CPU libre (para acomodar los procesos paralelos) as como suficiente I/ O. Los 50Gb deben estar repartidos sobre ms de un disco fsico para permitir que se produzcan varias lecturas concurrentes, habiendo suficientes canales I/ O desde el diso hasta el servidor para recuperar los datos desde el disco en paralelo.

Si tenis una tarea pequea, como las que se realizan en los sistemas OLTP, o tenemos pocos recursos disponibles, lo cual es tpico en los sistemas OLTP en donde la CPU y los recursos I/O estn usados al mximo, la ejecucin en paralelo no es algo que se deba considerar.

Una analoga del procesamiento en paralelo.Con frecuencia uso una analoga o smil para describir el procesamiento en paralelo y porque necesitamos una gran tarea y recursos suficientes en la base de datos. Supongamos que tenemos dos tareas para realizar. La primera es escribir un resumen de una pgina de cada nuevo producto. La segunda tarea es escribir un informe de diez pginas por captulo, tratando cada captulo temas muy independientes unos de otros. Cmo nos aproximaramos a cada tarea? Qu tarea creis que se beneficiara del procesamiento en paralelo?

Resumen de una pgina. En esta analoga el resumen de una pgina que nos han asignado no es una gran tarea. Podemos hacerla nosotros mismos o asignarla a otro individuo (si tenemos a alguien a nuestro cargo). Por qu? Porque la cantidad de trabajo para paralelizar este proceso excedera el trabajo de escribirlo en un papel nosotros mismos: tendramos que sentarnos, imaginarnos como deberan ser los 12 prrafos, determinar que prrafo no depende de los dems prrafos, hacer una reunin de equipo, elegir a 12 individuos o machacas , explicarles el problema y asignar a cada persona un prrafo, actuar como el coordinador y recoger todos sus prrafos al finalizar, secuenciarlos en el orden correcto, verificar que son correctos y por ltimo imprimir el informe. Todo esto es absurdamente ms largo que escribir el papel nosotros mismos, de forma serializada. La sobrecarga de manejar un gran grupo de personas en un proyecto de esta escala excede con mucho los beneficios de tener los 12 prrafos escritos en paralelo. El mismo principio se aplica a la ejecucin en paralelo en la base de datos. Si tenemos una tarea que tarda segundos o menos en completarse de forma serializada, introducir la ejecucin en paralelo y su sobrecarga asociada provocar que el proceso tarde an ms.

I nforme de diez pginas por captulo. Vamos a examinar ahora la segunda tarea. Si queremos escribir un informe con diez pginas por captulo tan rpido como sea posible, la forma ms lenta de hacerlo es encargar todo el trabajo a un nico individuo (creedme, lo digo por experiencia propia a la hora de traducir, interpretar, escribir y probar cada uno de los captulos de este cursillo). Aqu tendramos la reunin de equipo, revisaramos el proceso, asignaramos el trabajo, actuaramos como coordinadores, recogeramos los resultados, ordenaramos e imprimiramos el informe y lo entregaramos. Si tenemos un equipo de personas que actualmente no est haciendo nada repartir el trabajo de esta forma tiene sentido.

Sin embargo considerad que actuamos como el jefe, nuestro equipo es multitarea y tienen muchas tareas sobre sus mesas. En este caso debemos ir con cuidado en este gran proyecto. Necesitamos estar seguros de que no sobrecargamos a nuestro equipo: no queremos que nadie quede exhausto. No podemos delegar ms trabajo que los recursos que tenemos (= nuestro equipo) puede manejar, de lo contrario nos abandonarn. Si nuestro equipo ya est completamente utilizado aadir ms trabajo provocar que las agendas y los otros trabajos se retrasen. La ejecucin en paralelo en Oracle es exactamente igual. Si tenemos una tarea que tarda varios minutos, horas o das, la introduccin de la ejecucin en paralelo puede ser lo que haga que se ejecute ocho veces ms rpido. Pero si el sistema est bajo de recursos (= las otras tareas del equipo) la introduccin de la ejecucin en paralelo ser algo a evitar ya que el sistema se hundira an ms.

Si recordamos que no se debe llegar nunca a estos extremos ilgicos tendremos la mejor referencia para saber si podemos usar el paralelismo en cada caso. Si tenemos una tarea que tarda tres segundos es indudable que el paralelismo puede ser usado para hacerla ms rpida. Si el sistema anda escaso de recursos, el aadir paralelismo slo conseguir que las cosas vayan peor.

Consultas en paralelo.Las consultas en paralelo permiten que una nica sentencia SELECT sea dividida en varias sentencias ms pequeas, funcionando cada parte de forma concurrente y combinando finalmente los resultados para proporcionar los resultados solicitados. Considerar por ejemplo esta consulta: SELECT COUNT(status) FROM big_table; Usando consultas en paralelo esta consulta puede usar un nmero de sesiones paralelas, dividiendo la tabla BIG_TABLE en trozos pequeos y solicitando a cada sesin que lea su parte de la tabla y cuente sus filas. El coordinador de la consulta en paralelo para esta sesin recibir un subtotal desde cada una de las sesiones paralelas y calcular el total, devolviendo el resultado a la aplicacin del cliente. Grficamente esto sera como se ve en la siguiente figura:

Los procesos P000, P001, P002 y P003 son conocidos como servidores de ejecucin paralela (= parallel execution servers ) y a veces son denominados como esclavos de consulta paralela (PQ). Cada uno de estos

servidores de ejecucin paralela es una sesin separada conectada como si fuera un proceso dedicado. Cada uno de ellos es responsable de escanear una zona delimitada de la tabla BIG_TABLE, realizando un subtotal de su resultado y devolvindolos al servidor de coordinacin (la sesin original), la cual realizar el total de los diferentes subtotales y crear la respuesta final. Tambin podemos ver esto como un plan de ejecucin. Usando la tabla BIG_TABLE con 10 millones de filas en ella activaremos una consulta en paralelo y descubriremos como podemos ver una accin de este tipo. Este ejemplo fue realizado en un servidor con cuatro CPU, con los valores por defecto para todos los parmetros de paralelismo. Inicialmente nosotros esperaramos ver el siguiente plan: EXPLAIN PLAN FOR SELECT COUNT(status) FROM big_table; Explained. SELECT * FROM TABLE(dbms_xplan.display); PLAN_TABLE_OUTPUT ---------------------------------------Plan hash value: 1287793122 ---------------------------------------------------------------------------| Id | Operation | Name | Rows|Bytes | Cost (%CPU)|Time | ---------------------------------------------------------------------------| 0 | SELECT STATEMENT | | 1 | 17 | 32390 (2)|00:06:29 | | 1 | SORT AGGREGATE | | 1 | 17 | | | | 2 | TABLE ACCESS FULL| BIG_TABLE | 10M| 162M| 32390 (2)|00:06:29 | ----------------------------------------------------------------------------

ste es un tpico plan en serie. No hay paralelismo involucrado porque no solicitamos que la consulta en paralelo fuera activada, y por defecto no lo est. Podemos activar las consultas en paralelo de varias maneras, incluyendo el uso de un hint directamente en la consulta o alterando la tabla para activar que las rutas de ejecucin paralelas sean consideradas (opcin que usaremos ahora). Podemos dictaminar especficamente el grado de paralelismo que debe ser considerado en los planes de ejecucin sobre esta tabla, como por ejemplo as: ALTER TABLE big_table PARALLEL 4; Aunque yo prefiero decirle a Oracle: considera la ejecucin en paralelo pero calcula t el grado de paralelismo apropiado segn la carga del sistema y la consulta en si . Permito que el grado de paralelismo vare con el tiempo as como la carga del sistema sube o baja. Si contamos con todos los recursos disponibles el grado de paralelismo podr aumentar, y si los recursos estn limitados, tendr que ser menor. En vez de sobrecargar el servidor con un grado de paralelismo fijo, esta aproximacin permite a Oracle que dinmicamente aumente o disminuya la cantidad de procesos en paralelo requeridos por la consulta. Por lo tanto simplemente activar las consultas paralelas contra esta tabla con este comando: ALTER TABLE big_table PARALLEL; Y eso es todo (las consultas en paralelo sern consideradas ahora para las operaciones contra esta tabla). Reejecuto el plan de ejecucin, viendo esta vez lo siguiente: EXPLAIN PLAN FOR SELECT COUNT(status) FROM big_table; Explained. SELECT * FROM TABLE(dbms_xplan.display);
------------------------------------------------------------------------------------------------| Id | Operation | Name | Rows | Bytes | Cost | TQ |IN-OUT| PQ Distrib | ------------------------------------------------------------------------------------------------| 0 | SELECT STATEMENT | | 1 | 7 | 158 | | | | | 1 | SORT AGGREGATE | | 1 | 7 | | | | | | 2 | SORT AGGREGATE | | 1 | 7 | | 08,00 | P->S | QC (RAND) | | 3 | TABLE ACCESS FULL | BIG_TABLE | 1000K| 6835K| 158 | 08,00 | PCWP | | -------------------------------------------------------------------------------------------------

Como la salida desde Oracle9i contiene pocos detalles muestro aqu la versin desde Oracle 10g: ----------------------------------------------------------------------------|Id | Operation | Name |Cost(%CPU)| TQ |IN-OUT|PQ Distrib | ---------------------------------------------------------------------------| 0| SELECT STATEMENT | | 4465 (1)| | | | | 1| SORT AGGREGATE | | | | | | | 2| PX COORDINATOR | | | | | | | 3| PX SEND QC (RANDOM) | :TQ10000 | |Q1,00| P->S |QC (RAND) | | 4| SORT AGGREGATE | | |Q1,00| PCWP | | | 5| PX BLOCK ITERATOR | | 4465 (1)|Q1,00| PCWC | | | 6| TABLE ACCESS FULL| BIG_TABLE| 4465 (1)|Q1,00| PCWP | | -----------------------------------------------------------------------------

NOTA: Las columnas ROWS, BYTES y TIME han sido eliminados de este ltimo plan para que el resultado sea ms visible y concreto. El tiempo total para la consulta fue de 00:00:54 frente a la anterior previsin de 00:06:20 del plan serializado. Recordad que esto son estimaciones, no promesas!

Si leemos este plan desde abajo empezando en ID= 6, se ven los pasos descritos en la ltima figura. El "full table scan" debe ser dividido en varios "escaneos pequeos" (paso 5). Cada uno de ellos entregar el subtotal de COUNT(STATUS) (paso 4). Estos subresultados sern transmitidos al coordinador de consultas paralelas (pasos 2 y 3) que realizar el total (paso 1) y retornar la respuesta. Si se miran los procesos del servidor se vera que hay ms de uno para nuestra conexin. Cuando se implemente la ejecucin en paralelo generalmente lo ptimo es tener los datos repartidos sobre tanto dispositivos fsicos como sea posible. Esto se puede lograr de varias maneras: usando "RAID striping" sobre varios discos; usando ASM (que contiene "striping"); usando el particionamiento para separar fsicamente la tabla BIG_TABLE sobre varios discos; usando varios archivos de datos en un nico tablespace, permitiendo de esta manera a Oracle el asignar extensiones para el segmento BIG_TABLE entre muchos archivos.

Por lo general, la ejecucin paralela trabaja mejor cuando obtiene el mximo acceso a la mayor cantidad de recursos (CPU, memoria e I/ O). Sin embargo eso no significa que no se puede obtener ninguna ventaja de la consulta en paralelo si todos los datos estn en un nico disco, simplemente que los beneficios no serin tantos como si se usaran varios discos. La razn por la cual ganarais algo de velocidad en los tiempos de respuesta usando incluso un nico disco es que mientras un servidor de ejecucin en paralelo est contando filas no las est leyendo y viceversa. As dos servidores de ejecucin paralela pueden ser capaces de contar todas las filas en menos tiempo de lo que un plan serializado tardara. Asmismo podemos benefiarnos de las consultas en paralelo incluso en servidores con una nica CPU. Indudablemente un SELECT COUNT(*) serializado usar el 100% de la CPU de un servidor de este tipo (se pasar la mayor parte del tiempo realizando, y esperando por, operaciones I/ O fsicas al disco). La consulta en paralelo utilizar completamente todos los recursos (la CPU y el sistema I/O en este caso). Este ltimo punto me brinda la oportunidad de volver a lo que mencion al principio del tema: que las consultas en paralelo no son escalables. Si permitimos que cuatro sesiones realicen de forma simultnea consultas con servidores de ejecucin paralela en un servidor con una sola CPU probablemente veremos que sus tiempos de respuesta son ms grandes que si hubieran procesado sus sentencias de forma serializada. Cuantos ms procesos reclaman el uso de un recurso ms se tardar en satisfacer sus necesidades. Y recordad que las consultas en paralelo requieren dos cosas para ser ciertas. Primero: necesitis realizar una tarea muy larga (por ejemplo una consulta cuyo tiempo de ejecucin se mide en minutos, horas o das, no en segundos o microsegundos). Esto implica que la consulta en paralelo no es una solucin para ser aplicada en un sistema OLTP tpico, en donde no se realizan tareas muy largas de forma habitual. Activar la ejecucin paralela en

esos sistemas sera desastroso. Segundo: necesitamos amplios recursos de CPU, I/O y memoria. Si carecemos de alguno de ellos las consultas en paralelo llevarn a nuestro sistema al lmite, afectando al rendimiento y a los tiempos de ejecucin.

Operaciones DML en paralelo.La documentacin de Oracle limita el mbito del trmino DML (PDML) para incluir slo las operaciones INSERT, UPDATE, DELETE y MERGE (no incluye el SELECT normal). Durante una setencia PDML Oracle puede usar varios servidores de ejecucin paralela para realizar nuestras operaciones INSERT, UPDATE, DELETE o MERGE en lugar de un nico proceso serializado. En un servidor multi-CPU con una capacidad plena de ancho de banda I/O el potencial incrementa la velocidad en que pueden ser realizadas las operaciones DML en masa. Sin embargo no debemos ver a las operaciones PDML como una caracterstica para aumentar la velocidad de nuestras aplicaciones OLTP. Como dije previamente las operaciones en paralelo fueron diseadas para maximizar el uso del servidor. Fueron diseadas para que un nico usuario pudiera hacer uso completamente de todos los discos, CPU y memoria de un servidor. En ciertos sistemas warehouse (con muchsimos datos y unos cuantos usuarios) esto puede ser deseable. En un sistema OLTP (con muchsimos usuarios realizando transacciones muy cortas) no queremos dar a un usuario la capacidad de capturar todos los recursos del servidor. Esto puede parecer contradictorio: si usamos las consultas en paralelo para aumentar la capacidad del sistema, cmo puede ser que no sea escalable? Cuando se aplica a un sistema OLTP la frase es muy precisa. La consulta en paralelo no es algo que permita que el nmero de usuarios concurrentes aumente. La consulta en paralelo fue diseada para permitir que una nica sesin generara tanto trabajo como el haran 100 sesiones concurrentes. En nuestro sistema OLTP no queremos que un nico usuario genere el trabajo de 100 usuarios. PDML es til en grandes entornos warehouse o para facilitar las actualizaciones tipo bulk de grandes cantidades de datos. La operacin PDML es ejecutada de la misma forma que una consulta ditribuda sera ejecutada por Oracle, con cada servidor de ejecucin paralela actuando como un proceso separado en la base de datos. Cada trozo de la tabla es modificado por un thread separado con su propia en independiente transaccin (y con su propio segmento de UNDO afortunadamente). Despus de que todos los procesos han acabado se realiza el COMMIT de todas las transacciones independientes. La siguiente figura da una idea del uso de cuatro servidores de ejecucin paralela. Cada uno de ellos tiene su transaccin independiente, y las modificaciones hechas sern grabadas o no por la sesin PDML coordinadora:

Podemos observar que de hecho son transacciones independientes y separadas creadas por los servidores de ejecucin paralela. En una prueba activ explcitamente las operaciones PDML (estas operaciones se diferencian de las consultas en paralelo en que a menos que se soliciten no las obtendremos) mediante el comando: ALTER SESSION ENABLE PARALLEL DML; De hecho el que la tabla posea el atributo PARALLEL no es suficiente aqu, como si lo era para las consultas en paralelo. La razn tras esto es que existen ciertas limitaciones asociadas que explicar ms adelante. En la misma sesin anterior realizo un UPDATE de tipo bulk , debido ste a que he activado las PDML: UPDATE big_table SET status = done ;

Si obtengo el plan de ejecucin reducido de esta sentencia veo lo siguiente: ---------------------------------------------| Id | Operation | Name | ---------------------------------------------| 0 | UPDATE STATEMENT | | | 1 | PX COORDINATOR | | | 2 | PX SEND QC (RANDOM) | :TQ10001 | | 3 | INDEX MAINTENANCE | BIG_TABLE | | 4 | PX RECEIVE | | | 5 | PX SEND RANGE | :TQ10000 | | 6 | UPDATE | BIG_TABLE | | 7 | PX BLOCK ITERATOR | | | 8 | TABLE ACCESS FULL| BIG_TABLE | ----------------------------------------------

Como resultado de la implementacin de las operaciones PDML pseudo-distribudas las limitaciones asociadas son estas: los disparadores no estn soportados durante estas operaciones. Esta es una limitacin razonable en mi opinin ya que los disparadores tienden a aadir mucha sobrecarga a las operaciones UPDATE; ciertas restricciones de integridad referencial no estn soportadas durante estas operaciones, debido a que cada tabla es modificada por una transaccin separada en una sesin separada. Por ejemplo la integridad auto-referencial no est soportada; no podemos acceder a la tabla que est siendo modificada con PDML mientras no se realice un COMMIT o un ROLLBACK; la caracterstica disparadores; Advanced Replication no est soportada con PDML debido a que se basa en

las restricciones del tipo deferred tampoco est soportadas; las transacciones distribudas no son soportadas en PDML; las tablas clusterizadas no son soportadas en PDML.

Operaciones DDL en paralelo.Creo que las operaciones DDL en paralelo son el "punto caliente" en la tecnologa de paralelizacin de Oracle. Como hemos discutido, la ejecucin en paralelo generalmente no es apropiada para sistemas OLTP. De hecho para muchos sistemas "warehouse" las consultas en paralelo son cada vez menos una opcin a considerar. Considerad un "warehouse" ofrecido por una aplicacin web: literalmente puede ser accedido por miles de usuarios en el mismo momento, y obviamente no todos pueden ejercer la facultad de lanzar consultas en paralelo.

Pero un DBA o un programador realizando grandes operaciones de tipo "batch" durante una "ventana de tiempo" ya es una historia diferente. El DBA o el programador son personas individuales y pueden tener un servidor impresionante con toneladas de recursos de computacin disponibles. Slo tienen una cosa que hacer: cargar estos datos, reorganizar esta tabla, reconstruir ese ndice, etc. Sin la ejecucin paralela el DBA o el programador tendran graves dificultades para usar realmente todas las capacidades del hardware. Con la ejecucin paralela pueden usarlas. Los siguientes comandos DDL permiten la "paralelizacin": CREATE INDEX, varios servidores de ejecucin paralela pueden escanear la tabla, ordenar sus datos y escribir los segmentos de la estructura del ndice; CREATE TABLE AS SELECT, la consulta que ejecuta el SELECT puede ser realizada usando una consulta en paralelo y la carga de la tabla tambin puede ser hecha en paralelo; ALTER INDEX REBUILD, la estructura del ndice puede ser reconstruda en paralelo; ALTER TABLE MOVE, una tabla puede ser movida en paralelo; ALTER TABLE SPLIT | COALESCE PARTITION, las particiones de una tabla pueden ser divididas o unidas en paralelo; ALTER INDEX SPLIT PARTITION, un ndice de particin tambin puede ser dividido en paralelo.

Los cuatro primeros comandos adems funcionan para particiones de tablas o ndices (o sea, podemos MOVEr una particin individual de una tabla en paralelo). La activacin del paralelismo se consigue aadiendo la clusula PARALLEL al final de la sentencia. Para mi las operaciones DDL es donde la ejecucin paralela de Oracle proporciona un beneficio ms grande. Pueden ser usadas con consultas en paralelo para acelerar ciertas operaciones costosas en tiempo, desde mantenimientos programados a puestas en produccin nocturnas: no es lo mismo esperar cuatro horas a que se cree un ndice a que acabe en 20 minutos (as ganaremos bastante tiempo para descansar). As como vimos que las consultas en paralelo estn diseadas para un usuario final, las sentencias DDL en paralelo estn diseadas para el DBA y el programador.

Paralelismo en objetos PL/SQL.Ahora me gustara tratar dos tipos de paralelismo aplicado a objetos o procesos en PL/SQL: Funciones paralelas en tubera (= pipelined ), que son una caracterstica de Oracle. Paralelismo casero DIY (= Do-it-yourself ), que es la aplicacin a nuestras aplicaciones de las mismas tcnicas que Oracle aplica para realizar full table scans paralelos. El paralelismo DIY es ms un truco de programacin que algo propio de Oracle.

Supongo que muchas veces habis encontrado aplicaciones (normalmente procesos batch ) diseadas para ejecutarse de forma serializada como el siguiente procedimiento: Create procedure process_data As Begin For x in ( SELECT * FROM tabla ) Realizar un proceso complejo sobre X Actualizar otra tabla o insertar un registro en otra parte End loop End

En este caso las consultas paralelas de Oracle o las sentencias PDML no nos ayudarn nada (de hecho aqu la ejecucin paralela del SQL por Oracle slo causara que la base de datos consumiera ms recursos y tardara

ms). Si Oracle tuviera que ejecutar la simple consulta SELECT * FROM tabla en paralelo no habra un aparente aumento de la velocidad. Si Oracle tuviera que realizar en paralelo el UPDATE o INSERT despus del proceso complejo tampoco habra un efecto positivo, ya que despus de todo se tratara del UPDATE o INSERT de una sola fila. Hay una cosa obvia que podemos hacer aqu: usar el procesamiento en array para el UPDATE o INSERT de despus del proceso complejo. Sin embargo eso no nos dat un 50% o ms de reduccin sobre el tiempo de ejecucin, que es lo que estamos buscando. No os enfadis, definitivamente en este caso es necesario implementar el procesamiento en array de las modificaciones, pero eso no har que este proceso se ejecute mucho ms rpido, ya que esta parte est condicionada por la SELECT anterior. Ahora suponed que este proceso se activa de noche en un servidor con cuatro CPU y que es la nica tarea del sistema. Habis observado que slo una CPU es parcialmente usada en este servidor y que el sistema de discos no esta siendo muy usado. Adems este proceso tarda varias horas y cada da tarda un poco ms ya que se aaden ms datos. Necesitamos reducir el tiempo de ejecucin en varias unidades (necesitamos que funcione cuatro u ocho veces ms rpido). Qu podemos hacer? Hay dos aproximaciones que podemos usar. Una aproximacin sera implementar una funcin paralela en tubera , en la cual Oracle decidir el grado de paralelismo apropiado (asumiendo que habis optado por eso, lo cual es recomendable). La otra aproximacin es el paralelismo DIY . A continuacin trataremos ambas aproximaciones. Funciones paralelas en tubera Podemos tomar nuestro extremadamente serializado proceso PROCESS_DATA de antes y dejar que Oracle lo ejecute en paralelo por nosotros. Para realizar esto necesitamos girar la rutina de dentro a fuera. En lugar de seleccionar filas de alguna tabla, procesarlas e insertarlas en otra tabla, insertaremos en otra tabla los resultados de recuperar algunas filas y procesarlas. Eliminaremos el INSERT del final del bucle y lo reemplazaremos en el cdigo por la clusula PIPE ROW. Esta clusula permite que nuestra rutina PL/ SQL genere los datos de la tabla como su salida y as seremos capaces de realizar un SELECT sobre nuestro proceso PL/SQL. La rutina PL/SQL que fue usada para procesar proceduralmente los datos se convierte de hecho en una tabla y las filas que recuperamos y procesamos son las salidas. Un claro ejemplo que hemos visto muchas veces es el siguiente comando: Select * from table(dbms_xplan.display); Esta es una rutina PL/SQL que lee la tabla PLAN_TABLE, reestructura la salida y la procesa usando PIPE ROW para retornarla al cliente. Usaremos el mismo proceso de hecho, pero permitiremos que sea procesado en paralelo. En este ejemplo usaremos dos tablas: T1 y T2. T1 ser la tabla de donde leemos antes y T2 ser la tabla hacia donde moveremos la informacin. Asumiremos que esto ser un tipo de proceso que se ejecuta para capturar los datos transaccionales del da y convertirlos en los datos de un informe para el da siguiente. Las dos tablas que usaremos son estas: CREATE TABLE t1 TABLESPACE users AS SELECT object_id id, object_name text FROM all_objects; exec dbms_stats.set_table_stats (user,'T1',numrows=>10000000,numblks=>100000) CREATE TABLE t2 TABLESPACE users AS SELECT t1.*, 0 session_id FROM t1 WHERE 1=0;

Como todos habis deducido hemos usado DBMS_STATS para engaar al optimizador y hacerle creer que hay 10.000.000 de filas en la tabla de entrada y que est consumiendo 100.000 bloques de la base de datos. Eso es porque queremos simular una gran tabla. La segunda tabla T2 es simplemente una copia de la estrucura de la primera tabla con el aadido de la columna SESSION_ID. Esa columna ser usada para ver realmente como se produce el paralelismo.

Ahora necesitamos declarar objetos TYPE a travs de los cuales nuestra funcin pipelined retornar los datos. Estos objetos son simplemente una definicin de la salida del procedimiento que estamos convirtiendo. En este caso se parece a T2: CREATE OR REPLACE id text session_id / TYPE t2_type AS OBJECT ( NUMBER, VARCHAR2(30), NUMBER )

CREATE OR REPLACE TYPE t2_tab_type AS TABLE OF t2_type /

Ahora vayamos con la funcin pipelined , que ser simplemente el procedimiento original PROCESS_DATA reescrito. El procedimiento es ahora una funcin que produce filas. Accepta como entrada los datos a procesar como un REF CURSOR . La funcin retorna T2_TAB_TYPE, el tipo que acabamos de crear. Es una funcin pipelined que tiene PARALLEL_ENABLED . Esta clusula que usamos le indica a Oracle particiona los datos por cualquier mtodo que trabaje bien. No hacemos ninguna asuncin sobre el orden de los datos . Tambin podemos usar el particionamiento por hash o por rango sobre una columna especfica en el REF CURSOR . Eso implicara usar un REF CURSOR fuertemente definido para que el compilados supiera que columnas estn disponibles. El particionamiento por hash nicamente enviara la misma cantidad de filas a cada servidor de ejecucin paralelo para procesar los datos segn el hash de la columna especificada. El particionamiento por rango enviara los rangos no solapados de los datos a cada servidor de ejecucin paralela segn la clave de particionamiento. Por ejemplo, si hubiramos particionado en la columna ID cada servidor de ejecucin paralela podra tomar rangos de 1 a 1.000, de 1.001 a 20.000, de 20.001 a 30.000, etc. (los valores de ID en ese rango). Aqu slo queremos que los datos sean divididos. Como se haga esta divisin no es relevante para nuestro procesamiento. Tambin queremos ser capaces de comprobar que filas han sido procesadas por cada servidor de ejecucin paralela, por lo que hemos definido la variable local L_SESSION_ID y la hemos inicializado desde la vista V$MYSTAT. La definicin de la funcin pipelined podra ser esta: CREATE OR REPLACE FUNCTION parallel_pipelined( l_cursor IN sys_refcursor ) RETURN t2_tab_type PIPELINED PARALLEL_ENABLE ( PARTITION l_cursor BY ANY ) IS l_session_id NUMBER; l_rec T1%ROWTYPE; BEGIN SELECT sid INTO l_session_id FROM v$mystat WHERE ROWNUM=1; LOOP FETCH l_cursor INTO l_rec; EXIT WHEN l_cursor%NOTFOUND; -- Aqu va el proceso complejo PIPE ROW(t2_type(l_rec.id,l_rec.text,l_session_id)); END LOOP; CLOSE l_cursor; RETURN; END; /

Como se observa en la funcin simplemente recuperaremos una fila (o varias si usramos BULK COLLECT para procesar en array el REF CURSOR ), realizaremos nuestro complejo proceso sobre ese datos y se pasar a la tubera. Cuando el REF CURSOR haya agotado los datos cerraremos el cursor y se retornar el control. Y eso es todo. Ahora estamos listos para procesar los datos en paralelo dejando a Oracle que, en base a los recursos del sistema disponibles, use el grado de paralelismo ms adecuado. Vamos a verlo:

ALTER SESSION ENABLE PARALLEL DML; INSERT /*+ APPEND */ INTO t2(id,text,session_id) SELECT * FROM TABLE(parallel_pipelined (CURSOR(SELECT /*+ PARALLEL(t1) */ * FROM t1 ))); 26024 filas creadas. COMMIT;

Para ver lo que ha sucedido aqu podemos consultar los datos recin insertados agrupndolos por SESSION_ID para ver primero como han sido usados varios servidores de ejecucin paralela, y para ver tambin cuantas filas ha procesado cada uno: SELECT session_id, COUNT(*) FROM t2 GROUP BY session_id; SESSION_ID COUNT(*) ---------- ---------9 13426 11 12598

Aparentemente hemos usado dos servidores de ejecucin paralela para la parte SELECT de esta operacin, habiendo procesado cada uno unos 13.000 registros de media. Como podemos ver Oracle ha paralelizado nuestro proceso pero ha sido necesario una reescritura radical de un proceso original que tena una vida ms corta cuantos ms datos tena la tabla fuente. No obstante si no es posible una reescritura total del cdigo podemos estar interesados en la siguiente implementacin: el paralelismo casero o DIY . Paralelismo casero DI Y ( Do-it-yourself) Digamos que tenemos el mismo proceso que en la seccin anterior: el procedimiento simple serializado. No podemos perder tiempo en reescribirlo (o no sabemos como), pero necesitamos que se ejecute en paralelo. Cmo podemos hacerlo? Mi aproximacin particular se basa muchas veces en usar los rangos ROWID para dividir la tabla en unos cuantos rangos que no se solapen y que capten enteramente la tabla. Conceptualmente es muy similar a como Oracle realiza una consulta paralela. Si pensis en un full table scan los procesos de Oracle virtualmente dividen la tabla en varios trozos o tablas pequeas, cada uno de los cuales es procesado por un servidor de ejecucin paralela. Nosotros haremos lo mismo usando los rangos ROWID (en anteriores versiones de Oracle precisamente se usaba este mtodo de forma nativa por la base de datos). Usaremos la tabla BIG_TABLE de 1.000.000 de filas ya que esta tcnica funciona mejor en grandes tablas con muchas extensiones y el mtodo que usar depende de ellas. Cuantas ms extensiones haya mejor ser la distribucin de datos. As que una vez creada la tabla BIG_TABLE con 1.000.000 de filas crear la tabla T2 as: DROP TABLE t2; CREATE TABLE t2 AS SELECT object_id id, object_name text, 0 session_id FROM big_table WHERE 1=0;

Ahora usar las colas de tareas (o JOBS ) de la base de datos para procesar de forma paralela nuestro procedimiento. Programaremos unas cuantas tareas. Cada tarea ser nuestro procedimiento ligeramente modificado para que procese slo las filas de un determinado rango. Para soportar de manera eficiente nuestra cola de tareas usaremos una tabla de parmetros para pasar las entradas a nuestras tareas: CREATE TABLE job_parms ( job NUMBER PRIMARY KEY, lo_rid ROWID, hi_rid ROWID ) TABLESPACE users;

Esto nos permitir pasar el ID de la tarea a nuestro procedimiento, y as l podr consultar esta tabla y recuperar el rango de ROWID que deber procesar. Vayamos ahora con el procedimiento. El cdigo resaltado es el nuevo cdigo que he aadido: CREATE OR REPLACE PROCEDURE serial ( p_job IN NUMBER ) IS l_rec job_parms%rowtype; BEGIN SELECT * INTO l_rec FROM job_parms WHERE JOB = p_job; FOR x IN ( SELECT object_id id, object_name text FROM big_table WHERE rowid BETWEEN l_rec.lo_rid AND l_rec.hi_rid ) LOOP -- Aqu va el proceso complejo INSERT INTO t2 (id, text, session_id ) VALUES ( x.id, x.text, p_job ); END LOOP; DELETE FROM job_parms WHERE job = p_job; COMMIT; END; /

Como podis ver no se han hecho cambios significantes. Mucho del cdigo aadido ha sido simplemente para recoger las entradas y el rango de ROWID a procesar. El nico cambio en la lgica fue el aadido de las lneas del predicado destacadas. Ahora vamos a programar nuestra tarea. Usaremos una consulta compleja que no explicar que usa funciones analticas para dividir la tabla:
DECLARE l_job NUMBER; BEGIN FOR x IN ( SELECT dbms_rowid.rowid_create ( 1, data_object_id, lo_fno, lo_block, 0 ) min_rid, dbms_rowid.rowid_create ( 1, data_object_id, hi_fno, hi_block, 10000 ) max_rid FROM (SELECT DISTINCT grp,first_value(relative_fno) over (partition by grp order by relative_fno, block_id rows between unbounded preceding and unbounded following) lo_fno, first_value(block_id ) over (partition by grp order by relative_fno, block_id rows between unbounded preceding and unbounded following) lo_block, last_value(relative_fno) over (partition by grp order by relative_fno, block_id rows between unbounded preceding and unbounded following) hi_fno, last_value(block_id+blocks-1) over (partition by grp order by relative_fno, block_id rows between unbounded preceding and unbounded following) hi_block, sum(blocks) over (partition by grp) sum_blocks FROM ( SELECT relative_fno, block_id, blocks, trunc( (sum(blocks) over (order by relative_fno, block_id)-0.01) / (sum(blocks) over ()/8) ) grp FROM dba_extents WHERE segment_name = upper('BIG_TABLE') AND owner = user ORDER BY block_id) ), (SELECT data_object_id FROM user_objects where object_name = upper('BIG_TABLE') ) ) LOOP dbms_job.submit( l_job, 'serial(JOB);' ); INSERT INTO job_parms(job, lo_rid, hi_rid) VALUES ( l_job, x.min_rid, x.max_rid ); END LOOP; END; /

Este bloque PL/SQL tendr que programar hasta ocho tareas para nosotros (o menos si la tabla no puede ser dividida en ocho piezas debido a que no tenga suficientes extensiones o tamao). Podemos ver cuantas tareas se han programado y cuales son sus salidas con: SELECT * FROM job_parms; JOB ---------29 30 31 32 33 34 35 36 LO_RID -----------------AAAKBoAAJAAAAUJAAA AAAKBoAAJAAABkJAAA AAAKBoAAJAAAB0JAAA AAAKBoAAJAAACCJAAA AAAKBoAAJAAADwJAAA AAAKBoAAJAAAD+JAAA AAAKBoAAJAAAEMJAAA AAAKBoAAJAAAEaJAAA HI_RID -----------------AAAKBoAAJAAABkICcQ AAAKBoAAJAAAB0ICcQ AAAKBoAAJAAACCICcQ AAAKBoAAJAAACSICcQ AAAKBoAAJAAAD+ICcQ AAAKBoAAJAAAEMICcQ AAAKBoAAJAAAEaICcQ AAAKBoAAJAAAEqICcQ

8 filas seleccionadas. COMMIT;

El COMMIT permite que nuestras tareas (los jobs de Oracle) comiencen el procesamiento. Como tena en el archivo de parmetros de la base de datos (SPFILE) el parmetro JOB_QUEUE_PROCESSES a 10 (o a un valor que me permite la activacin de varias tareas al mismo tiempo) las ocho tareas se activaron y al poco tiempo finalizaron. Los resultados fueron los siguientes: SELECT session_id, COUNT(*) FROM t2 GROUP BY session_id; SESSION_ID COUNT(*) ---------- ---------29 60673 30 60569 31 60733 32 69569 33 60740 34 61175 35 60544 36 65997 8 filas seleccionadas.

Los procesos no han estado tan distribuidos como el paralelismo real de Oracle hubiera podido hacer pero son bastante buenos. Si recordais antes, vimos cuantas filas fueron procesadas por cada servidor de ejecucin paralela usando el paralelismo de Oracle, y la cantidad de las mismas fue muy parecida entre ellos (se diferenciaban en una o dos). Aqu tenemos que la mayora de tareas procesaron 60.000 filas.

Vous aimerez peut-être aussi