Archive for the ‘SQL y PL/SQL’ Category

h1

Una manera de guardar y comparar queries

February 7, 2010

Desde hace unas semanas estoy inmerso en un upgrade de 9iR2 a 10gR2, por question de sencillez y rapidez decidí usar el Real Application Testing para comparar los planes de ejecución las queries en las dos versiones, siempre puede haber cambios.

Durante la configuración del RAT se me surgió la idea de probar comparar las queries de una misma versión pero de las diferentes pruebas (de varias iteraciones) como una validación que se ha probado bien y el entorno de prueba es estable.

Es bastante sencillo

1. activar 10046 y capturar las sentencias, bind y planes de ejecución en prueba X.
2. activar 10046 y capturar las sentencias, bind y planes de ejecución en prueba Y.
3. crear una tabla de mapeo de objet_id y objet_name, exportarlo e importarlo en la 11gR2 (para RAT)
4. Cargar las trazas de la prueba X a la BD de 11gR2 (para RAT)
5. Cargar las trazas de la prueba Y a la BD de 11gR2 (para RAT)

Para paso 3:

create table mapping_table as
select object_id id, owner,substr(object_name,1,30) name
from dba_objects
where object_type not in (
‘CONSUMER GROUP’,'EVALUATION CONTEXT’,
‘FUNCTION’,'INDEXTYPE’,'JAVA CLASS’,
‘JAVA DATA’,'JAVA RESOURCE’,'LIBRARY’,
‘LOB’,'OPERATOR’,'PACKAGE’,
‘PACKAGE BODY’,'PROCEDURE’,'QUEUE’,
‘RESOURCE PLAN’,'SYNONYM’,'TRIGGER’,
‘TYPE’,'TYPE BODY’)
union all
select user_id id,username owner, null name from dba_users;

Para los pasos 4 y 5 se utiliza esto:


DECLARE
   mycur   DBMS_SQLTUNE.sqlset_cursor;
BEGIN
   DBMS_SQLTUNE.create_sqlset ('lsc_sieb_x');

   OPEN mycur FOR
      SELECT VALUE (p)
        FROM TABLE
                (DBMS_SQLTUNE.select_sql_trace
                         (DIRECTORY => 'PRUEBA_X_DIR',
                          file_name => 'sieb_%.trc',
                          mapping_table_name  => 'MAPPING_TABLE',
                          select_mode => DBMS_SQLTUNE.single_execution
                         )
                ) p;

   DBMS_SQLTUNE.load_sqlset (sqlset_name => 'lsc_sieb_x',
                             populate_cursor => mycur,
                             commit_rows => 1000
                            );

   CLOSE mycur;
END;
/

DECLARE
   mycur   DBMS_SQLTUNE.sqlset_cursor;
BEGIN
   DBMS_SQLTUNE.create_sqlset ('lsc_sieb_y');

   OPEN mycur FOR
      SELECT VALUE (p)
        FROM TABLE
                (DBMS_SQLTUNE.select_sql_trace
                          (DIRECTORY => 'PRUEBA_Y_DIR',
                           file_name => 'sieb_%.trc',
                            mapping_table_name => 'MAPPING_TABLE',
                           select_mode => DBMS_SQLTUNE.single_execution
                           )
                ) p;

   DBMS_SQLTUNE.load_sqlset (sqlset_name => 'lsc_sieb_y',
                             populate_cursor => mycur,
                             commit_rows => 1000
                            );

   CLOSE mycur;
END;
/


con esta query sacamos las queries que repiten en las dos pruebas, ordeno por buffer_gets y cpu porque el resto de metricas fluctuan bastante:

WITH dist_sql_id AS
     (SELECT   sql_id, COUNT (*)
          FROM dba_sqlset_statements
         WHERE sqlset_name in ('lsc_sieb_x', 'lsc_sieb_y')
      GROUP BY sql_id
        HAVING COUNT (*) > 1)
SELECT   a.sqlset_name, a.sql_id, executions, elapsed_time, cpu_time,
         buffer_gets, disk_reads, rows_processed, fetches,
         parsing_schema_name
    FROM dba_sqlset_statements a, dist_sql_id b
   WHERE a.sql_id = b.sql_id
     AND sqlset_name in ('lsc_sieb_x', 'lsc_sieb_y')
     AND parsing_schema_name = 'XXX'
ORDER BY 2, 1, 6, 5

Para procesar los datos de rendimiento podemos jugar con las funciones LAG(), LEAD(), añadir filtros como elapsed_time mayor que xxx segundos, buffer_gets, disk_reads etc.

Luego para comparar plan de ejecucion podemos utilizar la vista DBA_SQLSET_PLANS.

Basicamemte creo que es lo que hace SQL Performance Analyzer por debajo pero aqui lo hacemos más manual. Este método es sencillo y muy rapido, me acuerdo hace 4 años en un upgrade similar para llevar acabo todas estas tareas tardaba entre 6 a 8 semanas de trabajo y scripting para guardar y comparar 12000 queries.

En este upgrade tambien usare el TTS con Physical Standby ya que el tiempo de upgrade es muy reducido, 45 minutos. Con el método de TTS Y Physical Standby espero reducir el tiempo de upgrade a 9iR2 a 10gR2 a 15 minutos, esto ya sera otro post.

h1

Trigger sobre eventos de DDL

July 26, 2008

Al querer monitorizar las operaciones de DDL en una base de datos he encontrado un bug muy grave.

He creado 3 triggers para capturar las operaciones DROP, TRUNCATE y ALTER. Al parecer si esta activado el trigger de ALTER provoca errores de este tipo

SQL> drop table dept;
drop table dept
           *
ERROR at line 1:
ORA-00604: error occurred at recursive SQL level 2
ORA-06502: PL/SQL: numeric or value error: character string buffer too small

Lo peor es que la tabla dept sigue existiendo en el diccionario de datos pero si se intenta describir la tabla con DESC dept dice que no existe.

Parece que se ha corrompido el diccionario de datos, hasta ahora se que borrando el usuario se arregla pero logicamente esto no puede ser una solucion. La otra opcion es no utilizar el Trigger de ALTER, sin ese trigger los otros dos funcionan a la perfeccion.

Aftecta a las versiones 10.2.0.3 y 10.2.0.4 (las que he podido probar)

Asi que cuidadin!

h1

JDBC 9.x y las variables de Timestamp de Java

October 12, 2007

Yo personalmente no programo en Java, lo hice hace casi 10 años y desde hace 8 no lo he vuelto a hacer asi que la verdad es que ni se exactamente como se declara una variable de fechas en Java.

Hace poco en un cliente donde tenian bastantes problemas de rendimiento en una de sus aplicaciones el cual es de una muy elevada criticidad (afecta a los que viajamos ;-) ). La aplicacion esta hecha por J2EE sobre Oracle 9iR2 y como servidor de aplicaciones Bea Weblogic. Se veian queries muy simples contra tablas correctamente indexados realizando Full Table Scan, no era trivial porque lanzaba las mismas queries desde SQLPLUS y me iba de fabula asi que como casi siempre me acorde del bind peeking (los malditos!) pero despues de revisar unas 6 docenas de queries donde algunas no usaban bind variables y tenian el mismo problema de ir rapido en SQLPLUS y lento desde la aplicacion.

Despues de estar unos dias traceando y analizando las 200 trazas que genere me di cuenta que habia muchas ocurrencias de “Dump of memory” en las variables bind. Hablando con los desarrolladores los cuales me comentan que pasan las fechas de java como variables de timestamp a la base de datos. En las trazas muestar algo parecido a esto:

bind 5: dty=180 mxl=11(11) mal=00 scl=00 pre=00 oacflg=03 oacfl2=0 size=0 offset=120
bfp=40759cbc bln=11 avl=07 flg=01
value=
Dump of memory from 0x40759CBC to 0x40759CC3
40759CB0 0C0A6B78 [xk..]
40759CC0 00010101 [....]

Cuando value deberia de aparecer algo asi: value=”9/20/2007 9:20:0″ (dty=180 indica que la variable es del tipo Timestamp)

Cual es el problema? Es la version de JDBC, a partir de la version 9.x el JDBC deja de convertir las variables Timestamp de Java a Date, envia directamente a la base de datos tal cual porque Oracle 9i ya soporte este tipo de datos. La consecuencia es que el motor de la base de datos recibe la variable Timestamp y ve que el campo es un Date por lo tanto le aplica implicitamente un TO_DATE evitando el uso del indice.

La solucion es añadir v8compatible en las propiedades del JDBC. Y este problema ya he lo he visto en mas de un cliente.

h1

Adaptive Cursor Sharing II

August 31, 2007

Siguiendo con el post anterior he probado con otra distribucion de datos y pasando las variables de STATE del 0 al 10:

 
     STATE CNT_PER_STATE STATE_PERCENT 
---------- ------------- ------------- 
         0         10000            10 
         1            10           .01 
         2         15000            15 
         3            10           .01 
         4            10           .01 
         5         22000            22 
         6            10           .01 
         7            10           .01 
         8            10           .01 
         9         15000            15 
        10         37940         37.94  

Planes generados   

SQL_FULLTEXT                                                                     SQL_ID        PARSE_CALLS EXECUTIONS VERSION_COUNT INVALIDATIONS 
-------------------------------------------------------------------------------- ------------- ----------- ---------- ------------- ------------- 
select  /* ACS-LSCC */                                                           1pq13qpqfza6x          11         11             7             0 
        id, state, count(*) 
from t1 
where state = :state   

Y en vez de 6 planes ahora genera 7 asi que parece que la sospecha es correcta. El CBO hace bind peeking hasta que encuentre un punto estable, que puede ser despues de varias consultas y ve que la selectividad es my similar y para ahi.

h1

Adaptive Cursor Sharing I (ACS)

August 31, 2007

Esta semana que he estado probando lo de Adaptive Cursor Sharing mencionado en Mas alla de Bind Peeking. Hace cosas raras, hay que estudiarlo mas a fondo pero funciona, ahora lo veremos.

Generamos datos para tener una distribucion de datos no-uniforme:

 
SQL> @create 
SQL> drop table t1;   

Table dropped.   

SQL> 
SQL> create table t1 
  2  ( 
  3          id      number, 
  4          text1   varchar2(20), 
  5          text2   varchar2(20), 
  6          state   number 
  7  );   

Table created.   

SQL> 
SQL> insert into t1 
  2     with tdata as 
  3          (select rownum id 
  4             from all_objects 
  5            where rownum <= 1000) 
  6     select rownum, 
  7            dbms_random.string ('u', 20), 
  8            dbms_random.string ('u', 20), 
  9            case 
 10                   when rownum/100000  create index t1_i1 
  2  on t1(state);   

Index created.   

SQL> 
SQL> exec dbms_stats.gather_table_stats('LSC', 'T1', ESTIMATE_PERCENT => 20, - 
>                                    METHOD_OPT => 'FOR COLUMNS STATE SIZE 10', CASCADE => TRUE); -- PASAR HISTOGRAMS PARA QUE PODAMOS USAR ESTA FUNCIONALIDAD   

PL/SQL procedure successfully completed.   

SQL> 
SQL> select state, count(*) cnt_per_state, (count(*)*100)/nr state_percent 
  2  from t1, (select max(id) nr 
  3              from t1) 
  4  group by state, nr;   

     STATE CNT_PER_STATE STATE_PERCENT 
---------- ------------- ------------- 
         2            10           .01 
         0            10           .01 
         3            10           .01 
         8            10           .01 
         1            10           .01 
        10         99900          99.9 
         5            10           .01 
         4            10           .01 
         7            10           .01 
         9            10           .01 
         6            10           .01   

11 rows selected.  

Hemos generado unos datos que teoricamente utilizaremos indice para esos STATE que no sean el 10. Pues bien a principio esto era muy confuso porque no importaba el valor el caso es que nunca dejaba de usar el indice, incluso para el STATE 10 (deberia hacer un FTS). La prueba era usando esta query y pasando los valores del 0 a 10 a la bind variable :STATE, hasta llegar las 11 ejecuciones.

 
set autotrace trace exp stat 
var state number   

exec :state := 0 
select  /* ACS-LSCC */ 
        id, state, count(*) 
from t1 
where state = :state 
group by id, state;   

pause Press enter to continue   

exec :state := 1 
select  /* ACS-LSCC */ 
        id, state, count(*) 
from t1 
where state = :state 
group by id, state;   

pause Press enter to continue   

exec :state := 2 
select  /* ACS-LSCC */ 
        id, state, count(*) 
from t1 
where state = :state 
group by id, state;   

pause Press enter to continue 
etc etc ....................... hasta 10   

y durante cada ejecucion en otra sesion monitorizando v$sqlstats con esta query   

select /* ACS-QRY */ 
sql_fulltext, sql_id, parse_calls, executions, version_count, invalidations 
from v$sqlstats 
where sql_fulltext like '%ACS-LSC%' 
  and sql_fulltext not like '%ACS-QRY%'   

El caso es que el VERSION_COUNT siempre se quedaba en uno, es decir que no generaba mas que un plan de ejecucion, y me pregunto pero esto funciona? Hasta que le di la vuelta a las variables, en vez de pasar del 0 a 10 del 10 a 0, y magicamente a partir del valor 8 (el 9 ni se inmutaba) empieza a generar otro plan, el 7 generaba otro, el 6 otro pero el 5, 4 no han generado ningunplan nuevo hasta el STATE 3. Curioso para 11 valores donde 10 son iguales me ha generado 6 planes de ejecucion.

 
Estado final usando los valores del 10 al 0   

SQL_FULLTEXT                                                     SQL_ID        PARSE_CALLS EXECUTIONS VERSION_COUNT INVALIDATIONS 
---------------------------------------------------------------- ------------- ----------- ---------- ------------- ------------- 
select  /* ACS-LSCC */                                           1pq13qpqfza6x          11         11             6             0 
        id, state, count(*) 
from t1 
where state = :state  

Y los 6 planes de ejecucion donde solo voy a demostrar 2 porque el resto son iguales pero con diferente coste en Ilas operaciones de NDEX RANGE SCAN y TABLE ACCESS BY INDEX ROWID:

 
SQL> @XPLAN 
SQL> select * 
  2  from table(dbms_xplan.display_cursor('1pq13qpqfza6x', 0));   

PLAN_TABLE_OUTPUT 
------------------------------------------------------------------------------------------ 
SQL_ID  1pq13qpqfza6x, child number 0 
------------------------------------- 
select  /* ACS-LSCC */         id, state, count(*) from t1 where state 
= :state group by id, state   

Plan hash value: 136660032   

--------------------------------------------------------------------------- 
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     | 
--------------------------------------------------------------------------- 
|   0 | SELECT STATEMENT   |      |       |       |   245 (100)|          | 
|   1 |  HASH GROUP BY     |      |     1 |    16 |   245   (3)| 00:00:03 | 
|*  2 |   TABLE ACCESS FULL| T1   | 99998 |  1562K|   240   (1)| 00:00:03 | 
---------------------------------------------------------------------------   

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

   2 - filter("STATE"=:STATE)   

SQL> select * 
  2  from table(dbms_xplan.display_cursor('1pq13qpqfza6x', 1));   

PLAN_TABLE_OUTPUT 
------------------------------------------------------------------------------------------ 
SQL_ID  1pq13qpqfza6x, child number 1 
------------------------------------- 
select  /* ACS-LSCC */         id, state, count(*) from t1 where state 
= :state group by id, state   

Plan hash value: 2629508439   

-------------------------------------------------------------------------------------- 
| Id  | Operation                    | Name  | Rows  | Bytes | Cost (%CPU)| Time     | 
-------------------------------------------------------------------------------------- 
|   0 | SELECT STATEMENT             |       |       |       |     3 (100)|          | 
|   1 |  HASH GROUP BY               |       |     1 |    16 |     3  (34)| 00:00:01 | 
|   2 |   TABLE ACCESS BY INDEX ROWID| T1    |     5 |    80 |     2   (0)| 00:00:01 | 
|*  3 |    INDEX RANGE SCAN          | T1_I1 |     5 |       |     1   (0)| 00:00:01 | 
--------------------------------------------------------------------------------------   

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

   3 - access("STATE"=:STATE) 

La verdad es que no entiendo muy bien lo de 6 planes, pero pensando bien puede ser asi es ACS, se adapta a los diferentes valores y logicamente no va a parar con dos planes porque podria tener perfectamente 2,3 o 4 valores que necesiten FTS y no ir por el indice. Por ejemplo si tuviesemos esta distribucion de datos es logico hacer bind peeking cada dos por tres.

      STATE CNT_PER_STATE STATE_PERCENT
---------- ------------- -------------
         0         10000            10
         1            10           .01
         2         15000            15
         3            10           .01
         4            10           .01
         5         22000            22
         6            10           .01
         7            10           .01
         8            10           .01
         9         15000            15
        10         37940         37.94 

El caso es que esto parece un arma de doble filo ya que dependiendo de los valores que hay en el campo podriamos terminar con muchos cursores hijos. Habra que subir el shared pool cuando upgradeamos a 11g ó aumentar MEMORY_TARGET, MEMORY_MAX_TARGET si vamos a usar el Automatic Memory Management (AMM).
De todos modos si no generamos ningun tipo de histogramas esto no supondria ningun problema.

Y como dije en otro post que pasaria con PL/SQL? Usamos el siguien Procedure para ver lo que pasa.

 
create or replace procedure LSC$ASC 
as 
        l_id    number; 
        l_state number; 
        l_cnt   number; 
begin 
        for i in reverse 1..10 
        loop 
                select /* ACS-LSC */ 
                       id, state, count(*) 
                into   l_id, l_state, l_cnt 
                from   t1 
                where state = i 
                  and rownum < 2 
                group by id, state; 
        end loop; 
end; 
/   

resultado   

SQL_FULLTEXT                                                                     SQL_ID        PARSE_CALLS EXECUTIONS VERSION_COUNT INVALIDATIONS 
-------------------------------------------------------------------------------- ------------- ----------- ---------- ------------- ------------- 
SELECT ID, STATE, COUNT(*) FROM T1 WHERE STATE = :B1 AND ROWNUM < 2 GROUP BY ID, 52r38wma52qbp           1         10             1             0   

Como sospechaba, como PL/SQL no parsea mas que una vez (por eso nos gusta PL/SQL) solamente genera un plan de ejecucion.

Finalmente lo de que no genera planes diferentes si pasamos del 1 a 10 sospecho que es porque despues de varios intentos el CBO ve que el plan sigue siendo el mismo y asume que no es necesario generar planes diferentes. Lo tendre que probarlo.

Follow

Get every new post delivered to your Inbox.