jueves, 24 de septiembre de 2009

Reporte de Tablas sin estadisticas o con estadisticas viejas (stale) para una sesion dada

Una gran parte de los problemas repentinos de performance se da porque el optimizador arma un plan ineficiente producto de que las estadisticas actuales de las tablas, particiones o subparticiones involucradas no reflejan la realidad. Esto se debe a que los segmentos sufrieron un cambio de datos mayor al 10% y no se actualizaron las estadisticas en el catalogo. Uno de los datos con el que comenzamos a analizar este tipo de problema es el sid asociado a la sesion que esta ejecutando con demoras. Teniendo el sid el siguiente paso es ver la sentencia en ejecución y chequear si los segmentos referenciados cuentan con estadisticas frescas. Si la sentencia en cuestion es compleja y referencia varios segmentos nos demorará un tiempo revisar cada uno de los segmentos. Por tal motivo pensé en armar un query que basado principalmente en la vista dinamica v$sql_plan, obtiene los segmentos usados en los paths del plan de ejecucion y luego verifica si estan STALE o si estan nulas usando la vista dba_tab_statistics.
El script de abajo permite determinar automaticamente que tablas, particiones y subparticiones tienen estadisticas desactualizadas para un sid determinado.


set line 120
set pagesize 999
set verify off

col owner format a15
col table_name format a30
col partition_name format a30
col subpartition_name format a30

PROMPT
PROMPT "---------------------------------------------"
PROMPT "Reporte de Tablas con estadisticas STALE "
PROMPT "o nulas para una sesion dada "
PROMPT "---------------------------------------------"
ACCEPT sid PROMPT "Ingrese SID a evaluar: "

select st.owner owner,
st.table_name table_name,
st.partition_name partition_name,
st.subpartition_name subpartition_name
from v$session s,
v$sql_plan p,
dba_tab_statistics st
where s.sql_id = p.sql_id
and p.object_owner = st.owner
and p.object_name = st.table_name
and s.sid = &sid
and nvl(st.stale_stats,'YES') = 'YES'
and ((nvl(st.partition_position,1)
between
(case when (REGEXP_LIKE(nvl(p.partition_start,'a'),'[^[:digit:]]'))
then 1
else to_number(p.partition_start) end)
and (case when
(REGEXP_LIKE(nvl(p.partition_stop,'a'),'[^[:digit:]]'))
then 10000
else to_number(p.partition_stop) end))
or
(nvl(st.subpartition_position,1) between
(case when (REGEXP_LIKE(nvl(p.partition_start,'a'),'[^[:digit:]]'))
then 1
else to_number(p.partition_start) end)
and (case when
(REGEXP_LIKE(nvl(p.partition_stop,'a'),'[^[:digit:]]')) then 10000
else to_number(p.partition_stop) end))
)
/

set verify on


IMPORTANTE: Para asegurar que esten impactados los cambios mas recientes en la
vista dba_tab_statistics es recomendable flushear la memoria de la
siguiente forma: dbms_stats.flush_database_monitoring_info.

miércoles, 23 de septiembre de 2009

Reporte historico de tiempo de ejecucion máxima, mínima y promedio de sentencias SQL

Cualquier DBA que haya trabajado un tiempo administrando bases de datos de producción, seguramente fue consultado, y a veces acusado, debido a demoras en los procesos o reportes. Ante ese tipo de cuestionamientos, lo primero que tenemos que asegurar es si realmente el proceso esta demorado o si se trata de la percepción o ansiedad del usuario u operador. La unica manera de saber eso, es analizando la historia de ejecución de las sentencias involucradas en las rutinas afectadas. Como es sabido, desde 10g contamos con un completo repositorio que se actualiza automaticamente, que entre otras estadisticas y metricas posee información sobre las sentencias ejecutadas. La vista DBA_HIST_SQLSTAT recolecta para cada sentencia, el tiempo de ejecucion general, el tiempo en cpu, el tiempo en i/o, cantidad de ejecuciones, etc. Utilizando dicha información armé un query que muestra la ejecucion mas larga, la ejecucion mas corta y un promedio para cada sentencia registrada.
El query de abajo realiza las agregaciones (max,min y avg) para todas las sentencias ejecutadas durante toda la historia almacenada en AWR (por default 7 dias). Tambien se podria reescribir levemente la query para que dado un sql_id retorne los resultados particulares, recordemos que sql_id es la identificación unica desde 10g para las sentencias sql (antes se usaba hash_value para identificar univocamente una sentencia).


select sql_id,
to_char(trunc(max(max_ela_time)/60/60),'09')||
to_char(trunc(mod(max(max_ela_time),3600)/60),'09')||
to_char(mod(mod(max(max_ela_time),3600),60),'09') max_ela_time,
to_char(max(max_ela_time_dt),'DD/MM/YYYY HH24:MI') max_ela_time_dt,
to_char(trunc(min(min_ela_time)/60/60),'09')||
to_char(trunc(mod(min(min_ela_time),3600)/60),'09')||
to_char(mod(mod(min(min_ela_time),3600),60),'09') min_ela_time,
to_char(min(min_ela_time_dt),'DD/MM/YYYY HH24:MI') min_ela_time_dt,
to_char(trunc(avg(avg_ela_time)/60/60),'09')||
to_char(trunc(mod(avg(avg_ela_time),3600)/60),'09')||
to_char(mod(mod(avg(avg_ela_time),3600),60),'09') avg_ela_time
from
(select unique sql_id,
round((first_value(elapsed_time)
over (partition by sql_id order by elapsed_time desc))/executions/1000000) max_ela_time,
first_value(dt) over (partition by sql_id order by elapsed_time desc) max_ela_time_dt,
round((first_value(elapsed_time)
over (partition by sql_id order by elapsed_time))/executions/1000000) min_ela_time,
first_value(dt)
over (partition by sql_id order by elapsed_time) min_ela_time_dt,
round((avg(elapsed_time) over (partition by sql_id))/executions/1000000) avg_ela_time
from (select unique
ss.sql_id,
s.snap_id,
lag (s.snap_id) over (partition by s.startup_time,ss.sql_id order by ss.snap_id desc) snap_id_n,
ss.elapsed_time_total elapsed_time,
s.begin_interval_time dt,
lag (ss.elapsed_time_total)
over (partition by s.startup_time,ss.sql_id order by s.snap_id desc ) elapsed_time_n,
lag (s.begin_interval_time)
over (partition by s.startup_time,ss.sql_id order by s.snap_id desc ) dt_n,
executions_total executions,
lag (ss.executions_total)
over (partition by s.startup_time,ss.sql_id order by s.snap_id desc ) executions_n
from dba_hist_sqlstat ss,
dba_hist_snapshot s
where s.snap_id = ss.snap_id)
where elapsed_time > elapsed_time_n
and executions != 0)
group by sql_id
order by avg_ela_time desc
/

SQL_ID MAX_ELA_T MAX_ELA_TIME_DT MIN_ELA_T MIN_ELA_TIME_DT AVG_ELA_T
------------- --------- ---------------- --------- ---------------- ---------
89qyn4bbt03jq 00 05 24 22/09/2009 18:00 00 00 56 22/09/2009 06:00 00 03 06
gfjvxb25b773h 00 00 13 22/09/2009 17:48 00 00 13 22/09/2009 17:48 00 00 13
a1axyycsv1fb1 00 00 06 21/09/2009 18:00 00 00 06 21/09/2009 18:00 00 00 06
fqmpmkfr6pqyk 00 00 05 21/09/2009 12:00 00 00 05 21/09/2009 12:00 00 00 05
b7jn4mf49n569 00 00 05 21/09/2009 20:00 00 00 05 21/09/2009 20:00 00 00 05
4c1xvq9ufwcjc 00 00 03 22/09/2009 17:48 00 00 03 22/09/2009 17:48 00 00 03
06fhnfwzpzvug 00 00 03 22/09/2009 17:48 00 00 03 22/09/2009 17:48 00 00 03
ahtrk133zdqa5 00 00 02 21/09/2009 22:00 00 00 02 21/09/2009 22:00 00 00 02
bunssq950snhf 00 00 02 21/09/2009 19:00 00 00 02 21/09/2009 19:00 00 00 02
d92h3rjp0y217 00 00 01 23/09/2009 03:00 00 00 00 21/09/2009 19:00 00 00 01
abtp0uqvdb1d3 00 00 02 23/09/2009 08:00 00 00 00 22/09/2009 06:00 00 00 01
8bfst16kjukv6 00 00 01 22/09/2009 17:48 00 00 01 22/09/2009 17:48 00 00 01
6jgrbypm756nu 00 00 01 21/09/2009 11:00 00 00 01 21/09/2009 11:00 00 00 01

miércoles, 16 de septiembre de 2009

Como minimizar problemas luego de un upgrade de versión de Oracle (Caso2: Cambio del orden de evaluación de predicados)

En esta nueva nota les voy a contar un problema con el que se pueden encontrar al migrar desde version 8i hacia 9i o superior. El principal tema radica en la evolución constante que va teniendo el optimizador para estimar el costo de acceso a los datos de las sentencias. En la versión 7, donde apareció por primera vez el optimizador por costos, el costo se calculaba simplemente ponderando por la cantidad de requerimientos de lectura a disco. Esto, como es sabido, provocó un rechazo a cambiar de RBO a CBO bastante generalizado en su momento, dado que los planes de ejecución comenzaban a hacer cosas extrañas, causando importantes problemas de rendimiento generalizado. Por tal motivo, la mayoria de las compañias continuaron usando el optimizador por reglas, ya que les garantizaba que no se alteraran los planes y que no se destabilizaran las aplicaciones. El principal problema de usar solo los read request para generar el costo en Oracle 7 fue no considerar el caching que existe en distintos niveles.

A partir de 8i, se comenzó a ponderar por tipo de lectura (single reads y multiblock reads) y por tamaño y tiempo de lectura. Esto mejoró bastante la calidad de los planes generados y dió mayor confianza a las empresas para animarse a cambiar a CBO, mas que nada porque Oracle Corporation comenzó a incentivar fuertemente a salir de RBO, discontinuado a partir de 7.x (año 1992) y desoportado desde 10g. Si bien el comportamiento en 8i fue mucho mas estable faltaba tomar en cuenta algo muy importante, el tiempo de cpu.

Recien a partir de 9i se incluyó en el calculo del costo el tiempo insumido en procesamiento de cpu, anteriormente la formula para calcular solo tomaba en cuenta caracteristicas de i/o. En consecuencia con esta nueva variable de ponderación el optimizador puede cambiar el orden de evaluación de los predicados si estima que con ese nuevo orden se minimiza el tiempo de cpu. Este reordenamiento puede causar que ciertas consultas fallen en 9i+ y no en 8i. Este fallo generalmente se debe a una inconsistencia de datos, que antes quedaba "tapada" y que ahora al evaluar en otro orden genera un error, por ejemplo con una conversión implicita.

Vayamos a los ejemplos para graficar mejor este tema:

Primero voy a crear la siguiente tabla:

rop@DESA10G> create table t as
2 select to_char(mod(rownum,30)) c1,
3 rownum n1,
4 mod(rownum,30) n2
5 from dual
6 connect by rownum <= 5000; Tabla creada.

La tabla T tiene 3 columnas:
C1: solo tendrá valores 30 posibles valores (de 0 a 29) y es de tipo varchar2.
N1: tiene valores distintos del 1 al 5000.
N2: tiene los mismos valores de C1 pero es de tipo number.

Para simular estar en 8i, voy a cambiar el parametro de session "optimizer_features_enable" para que el optimizador se comporte con un optimizador de 8i.

rop@DESA10G> alter session set optimizer_features_enable = '8.1.7';

Sesión modificada.

Ahora, ejecuto una sentencia que cuenta la cantidad de filas filtrando la tabla
por los tres campos.

rop@DESA10G> ed
Escrito file afiedt.buf

1 explain plan for
2 select count(1) from t
3* where c1 = 1 and n1 = 1111 and n2 = 1
rop@DESA10G> /

Explicado.

rop@DESA10G> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------
Plan hash value: 1842905362

-----------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost |
-----------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 48 | 2 |
| 1 | SORT AGGREGATE | | 1 | 48 | |
|* 2 | TABLE ACCESS FULL| T | 1 | 48 | 2 |
-----------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - filter(TO_NUMBER("C1")=1 AND "N1"=1111 AND "N2"=1)

Note
-----
- cpu costing is off (consider enabling it)

18 filas seleccionadas.

Mirando la información predicados (Predicate Information) notamos dos cosas interesantes. Primero hay una nota que nos advierte que el costo por cpu esta en off. Lo cual es lógico, por lo que comenté mas arriba respecto a que 8i no ponderaba por cpu y si bien estamos en 10g, recordemos que cambié el comportamiento del optimizador a 8i. Segundo vemos que los predicados se evaluaron en orden y que hubo una conversión implicita (TO_NUMBER()).

Vuelvo a poner el optimizador a su valor default y me fijo evaluo el plan:

rop@DESA10G> alter session set optimizer_features_enable = '10.2.0.4';

Sesión modificada.

rop@DESA10G> explain plan for
2 select count(1) from t
3 where c1 = 1 and n1 = 1111 and n2 = 1;

Explicado.

rop@DESA10G> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------
Plan hash value: 1842905362

---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 48 | 5 (0)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 48 | | |
|* 2 | TABLE ACCESS FULL| T | 1 | 48 | 5 (0)| 00:00:01 |
---------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - filter("N1"=1111 AND "N2"=1 AND TO_NUMBER("C1")=1)

Note
-----
- dynamic sampling used for this statement

18 filas seleccionadas.

El plan no cambió, se hizo sampleo dinamico ya que no habia recolectado estadisticas, pero lo mas importante a destacar es el cambio en la evaluación de los filtros. Notemos que ahora la conversión implicita se dejó para lo ultimo. Por que hizo eso el optimizador?, bien, pensemos que una conversión implica ciclos de cpu y entonces, porque no mejor evaluarlo al final cuando seguramente queden menos filas, ya que se van filtrando con los dos predicados o filtros anteriores, y asi minimizar la cantidad de conversiones, no?. Como ya dijimos el optimizador en versiones 9i+ se preocupa por el costo de procesamiento de cpu y por lo tanto puede realizar ciertos ajustes (reordenamiento de predicados, merge de subqueries, etc) si con eso se reduce la utilización de cpu.

Analicemos mas en detalle el ejemplo y como se evalua con cpu costing en ON y en OFF


Con CPU Costing en OFF (8i)

filter(TO_NUMBER("C1")=1 AND "N1"=1111 AND "N2"=1)

El primer predicado (TO_NUMBER("C1")=1) evalua 5000 filas y como resultado de ese filtro se queda con 167 filas.
El segundo predicado (N1=1111) evalua 167 filas y se queda con 1.
El tercer predicado (N2=1) evalua 1 y devuelve 1 (el resultado del count()).

Con CPU Costing en ON (9i+)

filter("N1"=1111 AND "N2"=1 AND TO_NUMBER("C1")=1)


El primer predicado (N1=1111) evalua 5000 filas y solo devuelve 1 fila.
El segundo predicado (N2=1) compara la fila y se la pasa al siguiente predicado.
El tercer predicado (TO_NUMBER("C1")=1) evalua una sola fila y devuelve el resultado.

En base al ejemplo analizado se ve claramente que con cpu costing apagado se deben realizar 5000 operaciones implicitas contra 1 operacion implicita cuando se tiene activado el costeo por cpu. Con este ejemplo sencillo, pero representativo, se puede ver lo importante del ordenamiento de la evaluación para minimizar el uso de recurso de cpu y por ende mejorar el tiempo de respuesta general.


Ahora veamos como fijar el orden de evaluación sin cambiar el comportamiento general de la sesion usando el hint "ordered_predicates":

rop@DESA10G> ed
Escrito file afiedt.buf

1 explain plan for
2 select /*+ ordered_predicates */ count(1) from t
3* where c1 = 1 and n1 = 1111 and n2 = 1
rop@DESA10G> /

Explicado.

rop@DESA10G> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------
Plan hash value: 1842905362

---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 48 | 5 (0)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 48 | | |
|* 2 | TABLE ACCESS FULL| T | 1 | 48 | 5 (0)| 00:00:01 |
---------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - filter(TO_NUMBER("C1")=1 AND "N1"=1111 AND "N2"=1)

Note
-----
- dynamic sampling used for this statement

18 filas seleccionadas.

Con el hint la evaluación es ordenada, tal cual hubiese sido en 8i por default.
En el plan no se muestró el costo de cpu, pero consultando la tabla de soporte PLAN_TABLE podemos ver que costos fueron asignados de acuerdo al orden de evaluación.

rop@DESA10G> ed
Escrito file afiedt.buf

1 select filter_predicates,cpu_cost from plan_table
2* where filter_predicates is not null
rop@DESA10G> /

FILTER_PREDICATES CPU_COST
-------------------------------------------------- ----------
"N1"=1000 AND "N2"=1 AND TO_NUMBER("C1")=1 1302275
TO_NUMBER("C1")=1 AND "N1"=1111 AND "N2"=1 1802225

rop@DESA10G>

El costo de cpu al evaluar en 10g fue de 1302275 (con reordenamiento) y el costo de cpu sin ordenamiento fue de 1802225 (8i)

Como demostré con el ejemplo, el optimizador evalua los predicados de forma tal de optimizar el uso de cpu. Este cambio podría hacer fallar ciertos codigos que antes funcionaban y que tenian inconsistencias de datos, como por ejemplo, tener valores no numericos sobre campos varchar que deben tener numeros, y entonces al convertir implicitamente se genere un error de invalid number (ORA-01722). Queda claro que si ocurre esto es debido a un mal diseño, ya que una columna que aloja solo números no debería ser de tipo varchar, ya que de esta forma se promueve, por un lado inconsistencias en los datos y por otro lado, se afecta el rendimiento debido a las conversiones implicitas.

jueves, 10 de septiembre de 2009

Como minimizar problemas luego de un upgrade de versión de Oracle (Caso1: Ordenamiento implicito de GROUP BY en versiones anteriores a 10g R2)

Esta nota pretende ser la primera de una serie de notas en las que voy a comentarles problemas que se pueden dar luego de un upgrade de versión de base de datos. Este año estuve involucrado en unos cuantos upgrades de versiones, siempre hacia version 10g R2 pero partiendo desde distintas versiones base (8i, 9i R2 y 10g R1). Aqui en Argentina todavia son muy pocos los que migraron a 11g ya que es una practica habitual, y en mi opinión muy acertada, esperar al segundo release para upgradear. Al momento de esta nota acaba de salir el 11g R2, pero solo para linux, asi que estimo que en poco tiempo tendremos disponible el nuevo release para las plataformas unix "grandes", tales como solaris, aix, hp-ux y tambien para la familia de SO's de windows.

Es sabido que cada nueva versión de Oracle introduce nuevos features, y en especial features que cambian el comportamiento del optimizador y que producen cambios en los paths de los planes de ejecución. Los cambios en el optimizador son generalmente para mejorar el acceso a los datos y por ende reducir el tiempo de respuesta. Esta mejora en la "inteligencia" del optimizador no debería ocacionar cambios de comportamiento, a menos que no se cumplan las Buenas Practicas de confección de sentencias sql. Una mala práctica, y por desgracia bastante común, es confiar en el ordenamiento implicito que se da, por ejemplo, al usar distint/unique o en el ordenamiento que se produce con el group by. Este último es a veces innecesario y agrega un path implicito para ordenar que suma un tiempo mas antes de retornar la respuesta.

A partir de 10g R2 cambió el path SORT GROUP BY por el path HASH GROUP BY mejorando el rendimiento dado que no se infiere la necesidad de retornar el resultado ordenado. Todas las unidades de código que "confiaban" en este ordenamiento implicito y que necesitan por negocio un cierto orden van a comenzar a devolver resultados erroneos al upgradear a 10g R2 o versión superior, recordemos que las "Best Practices" dictan usar siempre ORDER BY cuando debe haber un orden ya que el comportamiento no esta garantizado a futuro.

Ahora, como suelo hacer, les voy a mostrar el ejemplo del group by, en próximas notas le voy a mostrar otros "issues" que pueden causar fuertes dolores de cabeza cuando no se detectan a tiempo.

Voy a usar mi conocida, y nunca bien ponderada, tablita de ejemplo T


rop@TEST10G> select * from v$version;

BANNER
----------------------------------------------------------------
Oracle Database 10g Enterprise Edition Release 10.2.0.4.0 - 64bi
PL/SQL Release 10.2.0.4.0 - Production
CORE 10.2.0.4.0 Production
TNS for Solaris: Version 10.2.0.4.0 - Production
NLSRTL Version 10.2.0.4.0 - Production


rop@TEST10G> create table t as select * from dba_objects;

Tabla creada.

rop@TEST10G> exec dbms_stats.gather_table_Stats(user,'T');

Procedimiento PL/SQL terminado correctamente.


rop@TEST10G> select object_type,count(1)
2 from t
3 group by object_type

OBJECT_TYPE COUNT(1)
------------------- ----------
INDEX 22538
JOB CLASS 2
CONTEXT 5
TABLE SUBPARTITION 18
TYPE BODY 174
INDEXTYPE 10
PROCEDURE 252
RESOURCE PLAN 4
RULE 4
JAVA CLASS 16417
SCHEDULE 1
TABLE PARTITION 625
WINDOW 2
WINDOW GROUP 1
JAVA RESOURCE 770
TABLE 22631
TYPE 1941
VIEW 3804
LIBRARY 150
FUNCTION 329
TRIGGER 565
PROGRAM 12
MATERIALIZED VIEW 3
DATABASE LINK 5
CLUSTER 10
SYNONYM 23307
PACKAGE BODY 807
QUEUE 27
CONSUMER GROUP 6
EVALUATION CONTEXT 14
RULE SET 19
DIRECTORY 16
UNDEFINED 6
OPERATOR 57
JAVA DATA 306
DIMENSION 5
SEQUENCE 2759
LOB 713
PACKAGE 866
JOB 18
INDEX PARTITION 617
LOB PARTITION 1
XML SCHEMA 26

43 filas seleccionadas.

El listado salio desordenado, veamos el plan que genera:

rop@TEST10G> explain plan for
2 select object_type,count(1)
3 from t
4 group by object_type;

Explicado.

rop@TEST10G> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------
Plan hash value: 2963600285

---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 26 | 208 | 339 (7)| 00:00:05 |
| 1 | HASH GROUP BY | | 26 | 208 | 339 (7)| 00:00:05 |
| 2 | TABLE ACCESS FULL| T | 99843 | 780K| 324 (2)| 00:00:04 |
---------------------------------------------------------------------------

9 filas seleccionadas.

El path es HASH GROUP BY en reemplazo de SORT HASH GROUP

Ahora, voy a usar, el truco mas rápido para salir del paso, tipo de solución "quick and dirty", pero solución al fin, que me ha salvado varias veces cuando se pasó por alto algún nuevo mecanismo y se comienzan a ver los problemas en plena hora pico o cuando cancelan procesos baths al dia siguiente del upgrade. Podemos setear a nivel sesion el parámetro "optimizer_features_enable", tambien se puede setear a nivel de hint con opt_param(param,valor), para hacer un flashback al comportamiento de un versión anterior. Como el caso de esta nota se da a partir de 10g R2, como estrategia siempre tomo la decisión de ir al upgrade anterior mas próximo donde funciona como antes, para asi estabilizar el comportamiento y no tener que retornar al release original. Como dije antes, en este caso la solución definitiva sera disparar un requerimiento de cambio de código y que el sector de desarrollo agregue el order by en las sentencias que necesitan del ordenamiento para funcionar correctamente.



rop@TEST10G> alter session set optimizer_features_enable = '10.1.0.5';

Sesión modificada.


rop@TEST10G> select object_type,count(1)
2 from t
3 group by object_type;

OBJECT_TYPE COUNT(1)
------------------- ----------
CLUSTER 10
CONSUMER GROUP 6
CONTEXT 5
DATABASE LINK 5
DIMENSION 5
DIRECTORY 16
EVALUATION CONTEXT 14
FUNCTION 329
INDEX 22538
INDEX PARTITION 617
INDEXTYPE 10
JAVA CLASS 16417
JAVA DATA 306
JAVA RESOURCE 770
JOB 18
JOB CLASS 2
LIBRARY 150
LOB 713
LOB PARTITION 1
MATERIALIZED VIEW 3
OPERATOR 57
PACKAGE 866
PACKAGE BODY 807
PROCEDURE 252
PROGRAM 12
QUEUE 27
RESOURCE PLAN 4
RULE 4
RULE SET 19
SCHEDULE 1
SEQUENCE 2759
SYNONYM 23307
TABLE 22631
TABLE PARTITION 625
TABLE SUBPARTITION 18
TRIGGER 565
TYPE 1941
TYPE BODY 174
UNDEFINED 6
VIEW 3804
WINDOW 2
WINDOW GROUP 1
XML SCHEMA 26

43 filas seleccionadas.

Cambié la version del optimizador y el resultado fue el esperado, salió ordenado. Miremos el nuevo plan:

rop@TEST10G> explain plan for
2 select object_type,count(1)
3 from t
4 group by object_type;

Explicado.

rop@TEST10G> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------
Plan hash value: 3156910365

---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 26 | 208 | 337 (6)| 00:00:05 |
| 1 | SORT GROUP BY | | 26 | 208 | 337 (6)| 00:00:05 |
| 2 | TABLE ACCESS FULL| T | 99843 | 780K| 322 (2)| 00:00:04 |
---------------------------------------------------------------------------

9 filas seleccionadas.

Ahora uso en antiguo path SORT GROUP BY y en consecuencia la salida no altero el orden
Por ultimo, agregamos el order by y vemos como se agrega (ahora explicitamente) un path para ordenar el resultado antes de retornarlo.

rop@TEST10G> ed
Escrito file afiedt.buf

1 explain plan for
2 select object_type,count(1)
3 from t
4 group by object_type
5* order by object_type
rop@TEST10G> /

Explicado.

rop@TEST10G> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------
Plan hash value: 3861070257

----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 26 | 208 | 354 (11)| 00:00:05 |
| 1 | SORT ORDER BY | | 26 | 208 | 354 (11)| 00:00:05 |
| 2 | HASH GROUP BY | | 26 | 208 | 354 (11)| 00:00:05 |
| 3 | TABLE ACCESS FULL| T | 99843 | 780K| 324 (2)| 00:00:04 |
----------------------------------------------------------------------------



Este es un caso interesante de cambio de comportamiento que saca a la luz problemas de mala programación, que en versiones anteriores pasaban desapercibidas. Por tal motivo es muy importante tener código de calidad ,y que no solo funcione, para evitar sorpresas a futuro. En la próxima nota les voy a mostrar otro caso de cambio interesante, relacionado con cambios en el orden de evaluación de predicados.