Ahora, por que una sentencia comienza a funcionar mal de un día para el otro?. La respuesta generalemte es que el CBO (optimizador por costos) cambió el plan para ejecutar dicha sentencia y el plan resultó ser poco performante. Este tipo de problemas fue el principal motivo por el cual muchas empresas demoraron años en dejar el RBO (optimizador por reglas) y comenzaron a usar CBO en sus sistemas productivos. Una de las formas de convencer a los gerentes de sistemas para implementar CBO era asegurarles que las consultas principales de las aplicaciones iban a mantener los mismos planes en el tiempo, lo cuál no suena muy lógico, ya que si cambia la cardinalidad, distribucion, selectividad, etc, no se le dará la oportunidad a Oracle a evaluar un mejor plan, pero se les garantizaba un funcionamiento mas estable. Una de las formas de fijar los planes para ciertas sentencias es usando outlines, pero desde 11g existe un mecanismo mas inteligente para evitar cambios no deseados en los planes. Ese nuevo feature se llama SQL Plan Baselines.
Por default, 11g tiene habilitado el manejo de SQL Baselines, lo cual permite comparar el plan de cada sentencia parseada con un historico de planes, previamente capturados. El historial de planes se denomina "SQL plan history" en donde se pueden encontrar distintos planes para cada sentencia con los siguiente información entre otras:
ACCEPTED: Si el plan se considera aceptable y con posibilidades de ejecutarse.
ENABLED : Si el plan esta activo y disponible
FIXED : Si el plan se usará en forma permanente para la sentencia
Cuando se evalua el plan para una sentencia se compara el plan con el "SQL Baselines" que son los planes del historial aceptados, y si ya existe se toma el plan almacenado, si el plan no existe se guarda en el historial, sin aceptar para ser analizado posteriormente. Esto asegura que no cambie el plan repentinamente.
Los planes del historial no aceptados se pueden "evolucionar" si resultan ser mejores que el mejor plan aceptado.
Ahora voy a mostrarles algunas pruebas para entender mejor como funciona todo esto,
Para poder trabajar con un caso sencillo pero representativo voy a crear y llenar una tabla T con un poco mas de 2M de registros y un indice sobre la columna object_id:
SQL> create table t as select * from dba_objects;
Table created.
SQL> insert into t select * from t;
68864 rows created.
SQL> insert into t select * from t;
137728 rows created.
SQL> /
275456 rows created.
SQL> /
550912 rows created.
SQL> /
1101824 rows created.
SQL> /
2203648 rows created.
SQL> create index t_idx on t(object_id);
Index Created.
Ahora voy poner en 1 el object_id de casí todas las filas, de forma tal de hacer una distribución ideal para full scan para la prueba.
SQL> update t set object_id=1 where rownum<=2000000; 2000000 rows updated. SQL> commit;
Commit complete.
SQL>
Antes de comenzar a ver los planes voy a activar la captura a nivel sesion, para ir guardando los planes en el "Sql Historial":
SQL> alter session set optimizer_capture_sql_plan_baselines=true;
Session altered.
La consulta que voy a usar para la prueba es la siguiente:
SQL> select count(unique object_name)
2 from t
3 where object_id=1;
Execution Plan
----------------------------------------------------------
Plan hash value: 162046943
--------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 79 | 4932 (1)| 00:01:00 |
| 1 | SORT GROUP BY | | 1 | 79 | | |
| 2 | TABLE ACCESS BY INDEX ROWID| T | 2223K| 167M| 4932 (1)| 00:01:00 |
|* 3 | INDEX RANGE SCAN | T_IDX | 2223K| | 4180 (1)| 00:00:51 |
--------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("OBJECT_ID"=1)
Note
-----
- dynamic sampling used for this statement
SQL>
Cono la tabla no tiene estadísticas se activa el dynamic_sampling (nivel 2 default) pero a pesar de eso no encuentra el plan ideal (usando full scan)
Para lograr que el CBO se de cuenta que le conviene ir por full scan en lugar de por indice, ya que casi todas las filas estan involucradas, recolectamos estadísticas y volvemos a ejecutar la consulta:
begin
dbms_stats.gather_table_stats (
ownname => user,
tabname => 'T',
cascade => TRUE,
method_opt => 'FOR ALL INDEXED COLUMNS SIZE AUTO');
end;
SQL> select count(unique object_name)
2 from t
3 where object_id=1;
Execution Plan
----------------------------------------------------------
Plan hash value: 1476560607
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 70 | 9030 (1)| 00:01:49 |
| 1 | SORT GROUP BY | | 1 | 70 | | |
|* 2 | TABLE ACCESS FULL| T | 2004K| 133M| 9030 (1)| 00:01:49 |
---------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("OBJECT_ID"=1)
Esta vez el optimizador,basandose en la info de distribución de la columna object_id obtenida de la info estadistica, se dió cuenta de que le conviene acceder por full scan.
Voy a usar el procedimiento print_table para mostrar en forma vertical los planes capturados para la sentencia de test. Uso la vista DBA_SQL_PLAN_BASELINES:
SQL> exec print_table('select * from dba_sql_plan_baselines where sql_handle=''SYS_SQL_aa67186957099
2f5''');
SIGNATURE : 12278809749759824629
SQL_HANDLE : SYS_SQL_aa671869570992f5
SQL_TEXT : select count(unique object_name)
from t
where object_id=1
PLAN_NAME : SYS_SQL_PLAN_570992f54b85249e
CREATOR : ROP
ORIGIN : AUTO-CAPTURE
PARSING_SCHEMA_NAME : ROP
DESCRIPTION :
VERSION : 11.1.0.6.0
CREATED : 19-MAR-09 11.00.26.000000 AM
LAST_MODIFIED : 19-MAR-09 11.00.26.000000 AM
LAST_EXECUTED :
LAST_VERIFIED :
ENABLED : YES
ACCEPTED : NO
FIXED : NO
AUTOPURGE : YES
OPTIMIZER_COST : 9029
MODULE : SQL*Plus
ACTION :
EXECUTIONS : 0
ELAPSED_TIME : 0
CPU_TIME : 0
BUFFER_GETS : 0
DISK_READS : 0
DIRECT_WRITES : 0
ROWS_PROCESSED : 0
FETCHES : 0
END_OF_FETCH_COUNT : 0
-----------------
SIGNATURE : 12278809749759824629
SQL_HANDLE : SYS_SQL_aa671869570992f5
SQL_TEXT : select count(unique object_name)
from t
where object_id=1
PLAN_NAME : SYS_SQL_PLAN_570992f5c8551679
CREATOR : ROP
ORIGIN : AUTO-CAPTURE
PARSING_SCHEMA_NAME : ROP
DESCRIPTION :
VERSION : 11.1.0.6.0
CREATED : 19-MAR-09 10.46.01.000000 AM
LAST_MODIFIED : 19-MAR-09 10.46.01.000000 AM
LAST_EXECUTED : 19-MAR-09 10.48.03.000000 AM
LAST_VERIFIED :
ENABLED : YES
ACCEPTED : YES
FIXED : NO
AUTOPURGE : YES
OPTIMIZER_COST : 4931
MODULE : SQL*Plus
ACTION :
EXECUTIONS : 0
ELAPSED_TIME : 0
CPU_TIME : 0
BUFFER_GETS : 0
DISK_READS : 0
DIRECT_WRITES : 0
ROWS_PROCESSED : 0
FETCHES : 0
END_OF_FETCH_COUNT : 0
-----------------
PL/SQL procedure successfully completed.
Vemos que para el sql_handler SYS_SQL_aa671869570992f5 hay dos planes posibles, uno se aceptó (el primero, que es el que accede por indice) y el segundo, que ya cuenta con estadisticas armó otro plan y se almacenó en el historico pero sin aceptar.
Voy a ejecutar la misma consulta de siempre para ver que plan toma:
SQL> select count(unique object_name)
2 from t
3 where object_id=1;
COUNT(UNIQUEOBJECT_NAME)
------------------------
41640
Execution Plan
----------------------------------------------------------
Plan hash value: 162046943
--------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 79 | 4932 (1)| 00:01:00 |
| 1 | SORT GROUP BY | | 1 | 79 | | |
| 2 | TABLE ACCESS BY INDEX ROWID| T | 2223K| 167M| 4932 (1)| 00:01:00 |
|* 3 | INDEX RANGE SCAN | T_IDX | 2223K| | 4180 (1)| 00:00:51 |
--------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("OBJECT_ID"=1)
Note
-----
- dynamic sampling used for this statement
- SQL plan baseline "SYS_SQL_PLAN_570992f5c8551679" used for this statement
Statistics
----------------------------------------------------------
54 recursive calls
43 db block gets
33312 consistent gets
36344 physical reads
5996 redo size
429 bytes sent via SQL*Net to client
381 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
1 rows processed
Que pasaría si cambiamos el valor default del parametro optimizer_use_sql_plan_baselines para que no tomé mas las SQL Baselines:
SQL> alter session set optimizer_use_sql_plan_baselines =false;
Session altered.
SQL> set autotr off
SQL> set autotr on
SQL> select count(unique object_name)
2 from t
3 where object_id=1;
COUNT(UNIQUEOBJECT_NAME)
------------------------
41640
Execution Plan
----------------------------------------------------------
Plan hash value: 1476560607
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 70 | 9030 (1)| 00:01:49 |
| 1 | SORT GROUP BY | | 1 | 70 | | |
|* 2 | TABLE ACCESS FULL| T | 1995K| 133M| 9030 (1)| 00:01:49 |
---------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("OBJECT_ID"=1)
Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets
32436 consistent gets
32425 physical reads
0 redo size
429 bytes sent via SQL*Net to client
381 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
1 rows processed
Tal como esperabamos, ahora accedió por full scan. Como sabemos que el plan correcto es el que accede por full scan, usando el paquete dbms_spm analizamos la performance y promovemos el plan con costo mayor pero con acceso full de forma tal de que sea considerado en la proxima ejecución:
SQL> select dbms_spm.evolve_sql_plan_baseline(sql_handle => 'SYS_SQL_aa671869570992f5') from dual;
DBMS_SPM.EVOLVE_SQL_PLAN_BASELINE(SQL_HANDLE=>'SYS_SQL_AA671869570992F5')
--------------------------------------------------------------------------------
-------------------------------------------------------------------------------
Evolve SQL Plan Baseline Report
-------------------------------------------------------------------------------
Inputs:
-------
SQL_HANDLE = SYS_SQL_aa671869570992f5
PLAN_NAME =
TIME_LIMIT = DBMS_SPM.AUTO_LIMIT
VERIFY = YES
DBMS_SPM.EVOLVE_SQL_PLAN_BASELINE(SQL_HANDLE=>'SYS_SQL_AA671869570992F5')
--------------------------------------------------------------------------------
COMMIT = YES
Plan: SYS_SQL_PLAN_570992f54b85249e
-----------------------------------
Plan was verified: Time used 45.079 seconds.
Failed performance criterion: Compound improvement ratio <= 1.03. Baseline Plan Test Plan Improv. Ratio ------------- --------- ------------- Execution Status: COMPLETE COMPLETE Rows Processed: 1 1 DBMS_SPM.EVOLVE_SQL_PLAN_BASELINE(SQL_HANDLE=>'SYS_SQL_AA671869570992F5')
--------------------------------------------------------------------------------
Elapsed Time(ms): 20750 8597 2.41
CPU Time(ms): 6718 3656 1.84
Buffer Gets: 33203 32444 1.02
Mirando la comparativa se observa que el ratio de mejora con el plan full scan y es 2.41 veces mejor. Que habrá pasado con el historial SQL?:
SQL> exec print_table('select * from dba_sql_plan_baselines where sql_handle=''SYS_SQL_aa67186957099
2f5''');
SIGNATURE : 12278809749759824629
SQL_HANDLE : SYS_SQL_aa671869570992f5
SQL_TEXT : select count(unique object_name)
from t
where object_id=1
PLAN_NAME : SYS_SQL_PLAN_570992f54b85249e
CREATOR : ROP
ORIGIN : AUTO-CAPTURE
PARSING_SCHEMA_NAME : ROP
DESCRIPTION :
VERSION : 11.1.0.6.0
CREATED : 19-MAR-09 11.00.26.000000 AM
LAST_MODIFIED : 19-MAR-09 11.09.14.000000 AM
LAST_EXECUTED :
LAST_VERIFIED :
ENABLED : YES
ACCEPTED : YES
FIXED : NO
AUTOPURGE : YES
OPTIMIZER_COST : 9029
MODULE : SQL*Plus
ACTION :
EXECUTIONS : 0
ELAPSED_TIME : 0
CPU_TIME : 0
BUFFER_GETS : 0
DISK_READS : 0
DIRECT_WRITES : 0
ROWS_PROCESSED : 0
FETCHES : 0
END_OF_FETCH_COUNT : 0
-----------------
SIGNATURE : 12278809749759824629
SQL_HANDLE : SYS_SQL_aa671869570992f5
SQL_TEXT : select count(unique object_name)
from t
where object_id=1
PLAN_NAME : SYS_SQL_PLAN_570992f5c8551679
CREATOR : ROP
ORIGIN : AUTO-CAPTURE
PARSING_SCHEMA_NAME : ROP
DESCRIPTION :
VERSION : 11.1.0.6.0
CREATED : 19-MAR-09 10.46.01.000000 AM
LAST_MODIFIED : 19-MAR-09 10.46.01.000000 AM
LAST_EXECUTED : 19-MAR-09 10.48.03.000000 AM
LAST_VERIFIED :
ENABLED : YES
ACCEPTED : YES
FIXED : NO
AUTOPURGE : YES
OPTIMIZER_COST : 4931
MODULE : SQL*Plus
ACTION :
EXECUTIONS : 0
ELAPSED_TIME : 0
CPU_TIME : 0
BUFFER_GETS : 0
DISK_READS : 0
DIRECT_WRITES : 0
ROWS_PROCESSED : 0
FETCHES : 0
END_OF_FETCH_COUNT : 0
-----------------
PL/SQL procedure successfully completed.
Ahora el nuevo plan fue aceptado y paso a ser parte del SQL Baselines. Probamos de ejecutar la sentencia de prueba para ver que pasa
SQL> select count(unique object_name)
2 from t
3 where object_id=1;
COUNT(UNIQUEOBJECT_NAME)
------------------------
41640
Execution Plan
----------------------------------------------------------
Plan hash value: 1476560607
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 70 | 9030 (1)| 00:01:49 |
| 1 | SORT GROUP BY | | 1 | 70 | | |
|* 2 | TABLE ACCESS FULL| T | 1995K| 133M| 9030 (1)| 00:01:49 |
---------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("OBJECT_ID"=1)
Note
-----
- SQL plan baseline "SYS_SQL_PLAN_570992f54b85249e" used for this statement
Statistics
----------------------------------------------------------
13 recursive calls
15 db block gets
32444 consistent gets
32425 physical reads
5592 redo size
429 bytes sent via SQL*Net to client
381 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
Escogió el nuevo plan "SYS_SQL_PLAN_570992f54b85249e" que había sido evolucionado recientemente ya que según su evaluación es superior al que estaba usando.
La próxima prueba será cambiar la distribución de la columna object_id de forma tal de cambiar todos las columnas menos una con valor 2 en lugar de 1. Con dicha selectividad deberia usar el indice.
SQL> update t
2 set object_id=2
3 where object_id=1
and rownum <= 1999999; 1999999 rows updated. SQL> commit;
Commit complete.
SQL> select count(unique object_name)
2 from t
3 where object_id=1;
Execution Plan
----------------------------------------------------------
Plan hash value: 1476560607
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 70 | 9030 (1)| 00:01:49 |
| 1 | SORT GROUP BY | | 1 | 70 | | |
|* 2 | TABLE ACCESS FULL| T | 1995K| 133M| 9030 (1)| 00:01:49 |
---------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("OBJECT_ID"=1)
Note
-----
- SQL plan baseline "SYS_SQL_PLAN_570992f54b85249e" used for this statement
Siguio utilizando el plan por full scan, por que?, el problema fue que no se afectó el plan ya que no estan actualizadas las estadisticas, veamos que paso cuando actualizamos:
SQL> begin
2 dbms_stats.gather_table_stats (
3 ownname => user,
4 tabname => 'T',
5 cascade => TRUE,
6 method_opt => 'FOR ALL INDEXED COLUMNS SIZE AUTO');
7 end;
8 /
PL/SQL procedure successfully completed.
SQL> select count(unique object_name)
2 from t
3 where object_id=1;
Execution Plan
----------------------------------------------------------
Plan hash value: 162046943
--------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 70 | 4 (0)| 00:00:01 |
| 1 | SORT GROUP BY | | 1 | 70 | | |
| 2 | TABLE ACCESS BY INDEX ROWID| T | 3 | 210 | 4 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | T_IDX | 3 | | 3 (0)| 00:00:01 |
--------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("OBJECT_ID"=1)
Note
-----
- SQL plan baseline "SYS_SQL_PLAN_570992f5c8551679" used for this statement
SQL>
Tomó el otro plan en SQL baselines ya que cdo generó el plan y lo comparó con el SQL Baselines pudo matchear con un plan existente.
El último test consiste en simular el comportamiento de stored outlines (ver la nota: "Usando Stored Outlines") para mas información. Con lo cual vamos a fijar un plan, es decir, le decimos al CBO que escoja dicho plan si o si.
SQL> declare
2 l_plan int;
3 begin
4 l_plan := dbms_spm.alter_sql_plan_baseline(sql_handle => 'SYS_SQL_aa671869570992f5',
5 plan_name => 'SYS_SQL_PLAN_570992f54b85249e',
6 attribute_name => 'fixed',
7 attribute_value => 'YES');
8 end;
9 /
PL/SQL procedure successfully completed.
SQL> exec print_table('select * from dba_sql_plan_baselines where sql_handle=''SYS_SQL_aa67186957099
2f5''');
PL/SQL procedure successfully completed.
SQL> set serverout on
SQL> exec print_table('select * from dba_sql_plan_baselines where sql_handle=''SYS_SQL_aa67186957099
2f5''');
SIGNATURE : 12278809749759824629
SQL_HANDLE : SYS_SQL_aa671869570992f5
SQL_TEXT : select count(unique object_name)
from t
where object_id=1
PLAN_NAME : SYS_SQL_PLAN_570992f54b85249e
CREATOR : ROP
ORIGIN : AUTO-CAPTURE
PARSING_SCHEMA_NAME : ROP
DESCRIPTION :
VERSION : 11.1.0.6.0
CREATED : 19-MAR-09 11.00.26.000000 AM
LAST_MODIFIED : 19-MAR-09 02.02.06.000000 PM
LAST_EXECUTED : 19-MAR-09 11.11.19.000000 AM
LAST_VERIFIED :
ENABLED : YES
ACCEPTED : YES
FIXED : YES
AUTOPURGE : YES
OPTIMIZER_COST : 9029
MODULE : SQL*Plus
ACTION :
EXECUTIONS : 0
ELAPSED_TIME : 0
CPU_TIME : 0
BUFFER_GETS : 0
DISK_READS : 0
DIRECT_WRITES : 0
ROWS_PROCESSED : 0
FETCHES : 0
END_OF_FETCH_COUNT : 0
-----------------
SIGNATURE : 12278809749759824629
SQL_HANDLE : SYS_SQL_aa671869570992f5
SQL_TEXT : select count(unique object_name)
from t
where object_id=1
PLAN_NAME : SYS_SQL_PLAN_570992f5c8551679
CREATOR : ROP
ORIGIN : AUTO-CAPTURE
PARSING_SCHEMA_NAME : ROP
DESCRIPTION :
VERSION : 11.1.0.6.0
CREATED : 19-MAR-09 10.46.01.000000 AM
LAST_MODIFIED : 19-MAR-09 10.46.01.000000 AM
LAST_EXECUTED : 19-MAR-09 10.48.03.000000 AM
LAST_VERIFIED :
ENABLED : YES
ACCEPTED : YES
FIXED : NO
AUTOPURGE : YES
OPTIMIZER_COST : 4931
MODULE : SQL*Plus
ACTION :
EXECUTIONS : 0
ELAPSED_TIME : 0
CPU_TIME : 0
BUFFER_GETS : 0
DISK_READS : 0
DIRECT_WRITES : 0
ROWS_PROCESSED : 0
FETCHES : 0
END_OF_FETCH_COUNT : 0
-----------------
PL/SQL procedure successfully completed.
Como vimos mas arriba, antes de fijar el plan, se escogía el plan que accede por el índice, veamos que pasa ahora despues de fijar el plan por full scan:
SQL> select count(unique object_name)
2 from t
3 where object_id=1;
Execution Plan
----------------------------------------------------------
Plan hash value: 1476560607
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 70 | 9030 (1)| 00:01:49 |
| 1 | SORT GROUP BY | | 1 | 70 | | |
|* 2 | TABLE ACCESS FULL| T | 3 | 210 | 9030 (1)| 00:01:49 |
---------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("OBJECT_ID"=1)
Note
-----
- SQL plan baseline "SYS_SQL_PLAN_570992f54b85249e" used for this statement
Se uso el plan fijado.
Como vimos, resulta interensante implementar este nuevo feature ya que tener un mecanismo inteligente de control de planes nos asegura mayor estabilidad de las consultas, y nos permite evaluar mejoras y promover los planes mas adecuados. Muchas veces los mismos desarrolladores conocen la mejor forma de acceder y podrían fomentar dicho acceso agregando baselines adecuados. En comparación con los stored outlines el funcionamiento de SQL Baselines es mucho mas flexible y dinamico. Una oportunidad para probar SQL Baselines es en las pruebas de upgrade de BD. Se puede capturar las sentencias de forma tal de armar un historial completo y luego exportar e importar las baselines (tal como se exportan e importan las estadisticas) en la base actualizada para asi hacer mas controlado el cambio de version. Luego los dba's iran viendo las consultas criticas en particular y promoviendo mejoras graduales para aprovechar la nueva versión de Oracle. También se podrían usar en los deployment de nuevas aplicaciones. Cada nueva app tiene un conjunto propio de sentencias que se podrían evaluar y luego generar baselines sobre ambientes de test o QA para que en momento de la puesta en producción, con los baselines previamente importados, garanticemos un funcionamiento similar a los de las pruebas y posteriormente, en forma gradua, ir viendo de evolucionar las consultas que tengan mejores planes sobre nuevo ambiente.
Ref 1: Performance & Tuning Guide (11g)
Ref 2: Baselines and Better Plans
Excelente nota, Pablo. Como siempre, muy interesante y clarito.
ResponderEliminarExcelente , en verdad te felicito por la nota. Me ha ayudado a resolver el requerimiento de un usuario. :)
ResponderEliminar