Lo he entendido y quiero demostrarlo!

Si has completado los posts de este capitulo puedes ponerte a prueba completando un Quiz de 10 preguntas. Puedes acceder al Quiz desde mi pagina personal en el sitio de la Comunidad Oracle Hispana.

Anuncios

Estructura de un indice bitmap

Composicion II - Piet Mondrian

Composicion II - Piet Mondrian

El proposito de todo indice es proveer punteros a las filas de una tabla que tienen un valor determinado. En un indice B-tree, este objetivo se logra almacenando una lista de rowids de las filas de la tabla con el valor clave. Oracle almacena cada valor clave en forma repetida para cada fila. En un indice bitmap, en vez de una lista de rowids, Oracle crea un mapa de bits para cada valor clave del indice.

Cada bit del mapa corresponde a un rowid posible. Si el bit esta en 1, significa que el rowid contiene dicho valor clave. Una funcion interna de Oracle convierte la posicion del bit en el rowid conrrespondiente, de modo tal que los indices bitmap ofrecen la misma funcionalidad que los indices B-tree, a pesar de la diferente representacion interna. Si la cantidad de valores diferentes del indice es chica, entonces el indice bitmap sera muy eficiente en cuanto al uso de espacio fisico.

Supongamos que tenemos la siguiente tabla de clientes:

CLIENTE        APELLIDO       REGION
101            PEREZ          NORTE
102            GARCIA         CENTRO
103            LOPEZ          SUR
104            SAN MARTIN     SUR
105            BROWN          CENTRO
106            CANEPA         CENTRO

La columna region tiene baja cardinalidad, ya que los valores posibles son muy pocos (NORTE, CENTRO, SUR). Hay solamente tres valores posibles para la region por lo tanto un indice bitmap seria apropiado para esta columna. Sin embargo, no es recomendable un indice bitmap para la columna CLIENTE o APELLIDO, dada su alta cardinalidad. Para estos casos un indice B-tree proveera una representacion y acceso mas eficiente.

La siguiente seria la representacion del indice bitmap para la columna REGION. El indice tiene tres mapas de bits, uno para cada region.

NORTE    CENTRO    SUR         
1        0         0
0        1         0
0        0         1
0        0         1
0        1         0
0        1         0

Cada entrada o bit en el indice bitmap se corresponde  a una sola fila en la tabla de clientes. El valor del bit dependera del valor correspondiente de la fila en la tabla. Por ejemplo, para la region NORTE el mapa de bits tiene un 1 en la primer posicion. Eso es porque la primer fila de la tabla de clientes tiene el valor NORTE en la columna REGION. Luego, el mapa de bits tiene todos ceros, indicando que el resto de las filas de la tabla no tiene clientes en la region NORTE.

Una sentencia SQL sobre esta tabla y con el indice bitmap, ser resolveria de la siguiente manera.

select count(*) from CLIENTES
    where REGION in ('NORTE','SUR');

Un indice bitmap puede resolver esta sentencia con gran eficiencia contando la cantidad de unos existentes en el mapa de bits resultante como se muestra en la siguiente figura:

NORTE   CENTRO   SUR   (NORTE O SUR)
1       0        0      1
0       1        0      0
0       0        1      1
0       0        1      1
0       1        0      0
0       1        0      0

La columna “NORTE O SUR” es el mapa de bits resultante utilizado para acceder a la tabla.

Adicionalmente y a diferencia de los indices B-tree, los indices bitmap pueden incluir filas con valore NULL dentro de la estructura del indice.  En cuanto a las tablas particionadas, los indices bitmap se pueden utilizar solo si son locales a la particion. Los indices bitmap globales no son soportados para tablas particionadas.

Como leer el plan de ejecucion

Para comprender como se debe leer un plan de ejecucion comencemos con un ejemplo muy simple. Veamos a continuacion un plan de ejecucion sencillo:

Query Plan
--------------------------------------------------
SELECT STATEMENT     [CHOOSE] Cost=1234
  TABLE ACCESS FULL EMPLEADOS [:Q65001] [ANALYZED]

Cuando analizamos un plan de ejecucion, el paso que esta indentado mas a la derecha es la primer operacion que se realiza para la ejecucion de la sentencia (mas adelante veremos como se hace para determinar el orden de los pasos cuandoleemos un plan de ejecucion). Por lo tanto, en nuestro ejemplo, la primer operacion es TABLE ACCESS FULL EMPLEADOS. Este paso indica que estamos haciendo un barrido o recorrido completo de la tabla EMPLEADOS. Una vez finalizado dicho recorrido, el conjunto de filas resultante sera pasado al nivel superior para que siga procesando el query. Es decir que los datos seran pasados a la operacion SELECT que es la ultima etapa de la sentencia de nuestro ejemplo.

[CHOOSE] indica cual es el valor de optimizer_goal para el query. Esto no necesariamente indica que el plan haya utilizado dicho objetivo. La unica forma de confirmarlo es mirando la variable  COST= que tambien es parte del plan de ejecucion. En el ejemplo el costo tiene valores, indicando que se utilizo CBO (optimizador basado en costos).

SELECT STATEMENT     [CHOOSE] Cost=1234

Si el plan de ejecucion hubiera dicho

SELECT STATEMENT     [CHOOSE] Cost=

entonces el optimizador utilizado seria RBO (optimizador basado en reglas).

Es posts posteriores veremos en detalle que es el optimizador y que significa CBO o RBO. El objetivo de este articulo es aprender a leer un plan de ejecucion, no a interpretarlo. Aprender a interpretarlo es un poco mas complejo pues requiere de muchos otros conceptos previos. Obviamente, todos estos conceptos seran tratados oportunamente en SqlEficiente. El numero que acompaña al costo es un valor que utiliza Oracle internamente a fines comparativos para determinar cual es el plan que tiene el mejor costo. Los valores de los costos no sirven para comparar sentencias diferentes.

[:Q65001] indica que esta operacion particular del query se ejecuta en paralelo. Es decir que sera ejecutada por procesos esclavos en paralelo en vez de ser ejecutada por un solo proceso en forma serializada.

[ANALYZED] indica que el objeto en cuestion (en nuestro caso, la tabla EMPLEADOS) ha sido analizado y hay estadisticas disponibles sobre dicho objeto para que las puede utilizar el optimizador basado en costos.

Para determinar el orden en que se ejecutan cada unos de los pasos del plan de ejecucion de la sentencia es necesario entender las siguientes relaciones PADRE-HIJO:

PADRE
  PRIMER HIJO
  SEGUNDO HIJO

En este ejemplo, la sentencia PRIMER HIJO se ejecuta primero, luego se ejecuta la sentecia SEGUNDO HIJO y por ultimo PADRE procesa la salida de ambos hijos.

Veamos a continuacion un caso mas complejo:

PADRE
  PRIMER HIJO
    PRIMER NIETO
  SEGUNDO HIJO

Aplicando los mismo principios, PRIMER NIETO es la primer operacion, en segundo lugar se ejecuta la operacion PRIMER HIJO seguida por SEGUNDO HIJO. Por ultimo, la operacion PADRE.

Estos mismos principios pueden utilizarse para aplicarlos a las operaciones reales de un plan de ejecucion. Veamos algunos ejemplos concretos.

Ejemplo 1

set autotrace traceonly explain
select ename,dname
  from empleados, deptos
 where empleados.deptno=deptos.deptno
   and dept.dname in ('TESORERIA','COMPRAS','VENTAS','OPERACIONES');
15 rows selected.

Este query produce el siguiente plan de ejecucion:

Execution Plan
---------------------------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=8 Bytes=248)
   1    0   HASH JOIN (Cost=3 Card=8 Bytes=248)
   2    1     TABLE ACCESS (FULL) OF 'DEPTOS' (Cost=1 Card=3 Bytes=36)
   3    1     TABLE ACCESS (FULL) OF 'EMPLEADOS' (Cost=1 Card=16 Bytes=304)

En el plan de ejecucion vemos dos columnas de numeros que preceden al texto de cada fila. El primer numero es el identificador de sentencia (tambien llamado ID). El segundo numero es el identificador del padre (tambien llamado parent ID) de la operacion en cuestion. La primer operacion no tiene identificador de  padre ya que es la primera de todas. El ID y el PARENT ID son utilizados por el generador de planes del optimizador para construir el plan de ejecucion. Los pasos del plan de ejecucion son indentados para indicar la jerarquia y dependencia de las operaciones.

En nuestro ejemplo, el plan de ejecucion comienza con ID=0

   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=8 Bytes=248)

ID=0 no tiene padre, pero tiene un hijo.
ID=0 es padre de ID=1. Ademas ID=0 depende de las filas que devuelva ID=1
Por lo tanto ID=1 debe ejecutarse si o si antes que ID=0

Pasemos a ID=1:

   1    0   HASH JOIN (Cost=3 Card=8 Bytes=248)

ID=1 es hijo de ID=0
ID=1 is padre de ID=2 y de ID=3 y depende de las filas que le devuelvan ambos hijos.
Por lo tanto ID=2 y ID=3 deben ejecutarse si o si antes que ID=1

Pasemos a ID=2:

   2    1     TABLE ACCESS (FULL) OF 'DEPTOS' (Cost=1 Card=3 Bytes=36)

ID=2 es el primer hijo de ID=1
ID=2 no tiene hijos.
Este es el primer paso en la ejecucion de este query.
Las filas retornadas por esta operacion seran provistas a ID=1.

ID=1 tambien depende de ID=3:

   3    1     TABLE ACCESS (FULL) OF 'EMPLEADOS' (Cost=1 Card=16 Bytes=304)

ID=3 es el segundo hijo de ID=1
ID=3 no tiene hijos.
Esta es la seguna operacion que se ejecuta en este query.
Las filas retornadas seran provistas a ID=1.

ID=1 procesa las filas quer recibe de las operaciones de las cuales depende (ID=2 y ID=3) y retorna las filas a su padre
ID=0.
ID=0 retorna las filas al usuario.

En resumen:

La ejecucion comienza con ID=0: SELECT STATEMENT que depende de sus hijos para resolver el query por lo tanto ejecuta a su primer hijo ID=1 PID=0 HASH JOIN que a su vez depende de otros objetos hijo y por lo tanto ejecuta a su primer hijo ID=2 PID=1   TABLE ACCESS (FULL) OF ‘DEPTOS’  y luego a su segundo hijo ID=3 PID=2   TABLE ACCESS (FULL) OF ‘EMPLEADOS’
Las filas van siendo retonadas a los padres hasta llegar al final.