Optimización SQL en Oracle

Manejo de subconsultas en la cláusula SELECT. Parte I.

2013-06-17 · Blog

El motor SQL de Oracle es muy flexible. Se permite el uso de subconsultas en la cláusula WHERE, y HAVING, así como directamente en la cláusula FROM (se tratan como vistas) y también como forma de resolver una columna en el conjunto de resultados en la cláusula SELECT.

Lo curioso es que, dado que el formateo de filas se hace al final, de algún modo el optimizador ignora el coste de combinación de las tablas de la subconsulta, pues las trata una vez ha procesado la sentencia.

Por ejemplo, la consulta de clientes con su identificación de sexo y estado civil.

SQL> set autotrace traceonly

SQL> select nif, cn_sexo sexo, cn_ec estado

2  from clientes, sexos, estadosciviles

3  where clientes.ec_id_ec=estadosciviles.id_ec

4    and clientes.sex_id_sexo=sexos.id_sexo;

9999 filas seleccionadas.

Plan de Ejecución

———————————————————-

Plan hash value: 3779357754

————————————————————————————————

| Id  | Operation                     | Name           | Rows  | Bytes | Cost (%CPU)| Time     |

————————————————————————————————

|   0 | SELECT STATEMENT              |                | 10001 |   341K|    34   (6)| 00:00:01 |

|*  1 |  HASH JOIN                    |                | 10001 |   341K|    34   (6)| 00:00:01 |

|   2 |   TABLE ACCESS FULL           | ESTADOSCIVILES |     5 |    55 |     3   (0)| 00:00:01 |

|   3 |   MERGE JOIN                  |                | 10000 |   234K|    31   (7)| 00:00:01 |

|   4 |    TABLE ACCESS BY INDEX ROWID| SEXOS          |     2 |    16 |     2   (0)| 00:00:01 |

|   5 |     INDEX FULL SCAN           | SEX_PK         |     2 |       |     1   (0)| 00:00:01 |

|*  6 |    SORT JOIN                  |                |  9999 |   156K|    29   (7)| 00:00:01 |

|   7 |     TABLE ACCESS FULL         | CLIENTES       |  9999 |   156K|    27   (0)| 00:00:01 |

————————————————————————————————

Predicate Information (identified by operation id):

—————————————————

1 – access(«CLIENTES».»EC_ID_EC»=»ESTADOSCIVILES».»ID_EC»)

6 – access(«CLIENTES».»SEX_ID_SEXO»=»SEXOS».»ID_SEXO»)

filter(«CLIENTES».»SEX_ID_SEXO»=»SEXOS».»ID_SEXO»)

Estadísticas

———————————————————-

1  recursive calls

0  db block gets

108  consistent gets

1  physical reads

0  redo size

346739  bytes sent via SQL*Net to client

7846  bytes received via SQL*Net from client

668  SQL*Net roundtrips to/from client

1  sorts (memory)

0  sorts (disk)

9999  rows processed

La lectura de 108 bloques en memoria corresponde mayoritariamente a los bloques de la tabla CLIENTES, al índice de clave primaria de la tabla SEXOS y al acceso completo por FULL SCAN de la tabla ESTADOSCIVILES. El coste se tasa en 34.

Esta misma consulta, expresando la resolución del valor de sexo y estado civil del cliente directamente en las columnas de resultado, suponen un plan de ejecución similar, pero tasado ligeramente inferior.

SQL> select nif,

2         (select cn_sexo from sexos where id_sexo=clientes.sex_id_sexo) sexo,

3         (select cn_ec from estadosciviles where id_ec=clientes.ec_id_ec) estado

4  from clientes;

9999 filas seleccionadas.

Transcurrido: 00:00:00.07

Plan de Ejecución

———————————————————-

Plan hash value: 3745735041

———————————————————————————————-

| Id  | Operation                   | Name           | Rows  | Bytes | Cost (%CPU)| Time     |

———————————————————————————————-

|   0 | SELECT STATEMENT            |                |  9999 |   156K|    27   (0)| 00:00:01 |

|   1 |  TABLE ACCESS BY INDEX ROWID| SEXOS          |     1 |     8 |     1   (0)| 00:00:01 |

|*  2 |   INDEX UNIQUE SCAN         | SEX_PK         |     1 |       |     0   (0)| 00:00:01 |

|   3 |  TABLE ACCESS BY INDEX ROWID| ESTADOSCIVILES |     1 |    11 |     1   (0)| 00:00:01 |

|*  4 |   INDEX UNIQUE SCAN         | EC_PK          |     1 |       |     0   (0)| 00:00:01 |

|   5 |  TABLE ACCESS FULL          | CLIENTES       |  9999 |   156K|    27   (0)| 00:00:01 |

———————————————————————————————-

Predicate Information (identified by operation id):

—————————————————

2 – access(«ID_SEXO»=:B1)

4 – access(«ID_EC»=:B1)

Estadísticas

———————————————————-

1  recursive calls

0  db block gets

766  consistent gets

0  physical reads

0  redo size

380260  bytes sent via SQL*Net to client

7846  bytes received via SQL*Net from client

668  SQL*Net roundtrips to/from client

0  sorts (memory)

0  sorts (disk)

9999  rows processed

Prácticamente, los costes de acceso y gestión de la tabla SEXOS y ESTADOSCIVILES parecen omitirse en el cálculo (el anterior plan medía 34, menos 3 de acceso a cada tabla y 1 de acceso al índice), pero el volumen total de bloques leídos en memoria se multiplica por siete.

Traza de ejecución con uso de joins

select nif, cn_sexo sexo, cn_ec estado

from clientes, sexos, estadosciviles

where clientes.ec_id_ec=estadosciviles.id_ec

and clientes.sex_id_sexo=sexos.id_sexo

call     count       cpu    elapsed       disk      query    current        rows

——- ——  ——– ———- ———- ———- ———-  ———-

Parse        1      0.00       0.00          0          0          0           0

Execute      1      0.00       0.00          0          0          0           0

Fetch      668      0.01       0.04          1        108          0        9999

——- ——  ——– ———- ———- ———- ———-  ———-

total      670      0.01       0.04          1        108          0        9999

Misses in library cache during parse: 1

Optimizer mode: ALL_ROWS

Parsing user id: 91  (VUELOS)

Number of plan statistics captured: 1

Rows     Execution Plan

——-  —————————————————

0  SELECT STATEMENT   MODE: ALL_ROWS

9999   HASH JOIN

5    TABLE ACCESS   MODE: ANALYZED (FULL) OF ‘ESTADOSCIVILES’  (TABLE)

9999    MERGE JOIN

2     TABLE ACCESS   MODE: ANALYZED (BY INDEX ROWID) OF ‘SEXOS’ (TABLE)

2      INDEX   MODE: ANALYZED (FULL SCAN) OF ‘SEX_PK’ (INDEX (UNIQUE))

9999     SORT (JOIN)

9999      TABLE ACCESS   MODE: ANALYZED (FULL) OF ‘CLIENTES’ (TABLE)

Traza de ejecución con subconsultas en la cláusula SELECT

select nif,

(select cn_sexo from sexos where id_sexo=clientes.sex_id_sexo) sexo,

(select cn_ec from estadosciviles where id_ec=clientes.ec_id_ec) estado

from clientes

call     count       cpu    elapsed       disk      query    current        rows

——- ——  ——– ———- ———- ———- ———-  ———-

Parse        1      0.00       0.00          0          0          0           0

Execute      1      0.00       0.00          0          0          0           0

Fetch      668      0.00       0.01          0        766          0        9999

——- ——  ——– ———- ———- ———- ———-  ———-

total      670      0.00       0.01          0        766          0        9999

Misses in library cache during parse: 1

Optimizer mode: ALL_ROWS

Parsing user id: 91  (VUELOS)

Number of plan statistics captured: 1

Rows     Execution Plan

——-  —————————————————

0  SELECT STATEMENT   MODE: ALL_ROWS

2   TABLE ACCESS   MODE: ANALYZED (BY INDEX ROWID) OF ‘SEXOS’ (TABLE)

2    INDEX   MODE: ANALYZED (UNIQUE SCAN) OF ‘SEX_PK’ (INDEX (UNIQUE))

5   TABLE ACCESS   MODE: ANALYZED (BY INDEX ROWID) OF ‘ESTADOSCIVILES’ (TABLE)

5    INDEX   MODE: ANALYZED (UNIQUE SCAN) OF ‘EC_PK’ (INDEX (UNIQUE) )

9999   TABLE ACCESS   MODE: ANALYZED (FULL) OF ‘CLIENTES’ (TABLE)

La ejecución mediante subconsultas parece indicar al optimizador que no es necesario ordenar la tabla CLIENTES para facilitar las joins, y que el obtener por cada fila desde memoria las filas correspondientes al sexo y al estado civil resulta óptimo.

Es algo muy parecido a una ejecución en estrella. La diferencia está en el cálculo de un hash para resolver los valores de sexo y estado civil, o el acceso directo a los bloques. Aunque la segunda ejecución parezca ejecutarse en menos tiempo, hay que tener en cuenta el alto consumo de bloques en memoria, cosa que no sucedería en una ejecución en estrella.

En este caso, la ejecución con subconsultas en la cláusula SELECT sustituyendo las joins ha supuesto una mejora del rendimiento. En la parte II publicaré un caso completamente opuesto. Mientras que los costes en el plan de ejecución aparentemente van siendo cada vez menores, el rendimiento real de las ejecuciones resulta cada vez peor.

← Volver al blog