Mostrando entradas con la etiqueta Scripts. Mostrar todas las entradas
Mostrando entradas con la etiqueta Scripts. Mostrar todas las entradas

viernes, 14 de enero de 2011

Análisis de consumo de espacio REDO global, por cada sesión y por cada sentencia

El consumo de espacio de redo (redo consumption) es algo inevitable, aunque es posible minimizarlo en ciertos casos y con ciertas operaciones, no se puede cancelar por completo. El redo es necesario para asegurar que ante una caida imprevista de la base, los datos en los bloques modificados, commiteados y todavia no persistidos en disco (dirty blocks), puedan recuperarse al levantar nuevamente la base con un proceso automatico denominado "rolling forward".

Si la base esta en modo archivelog, cada redo log se copia aparte para que no se sobreescriba y asi permitir, cuando se lo requiera, por ejemplo, poder realizar backup con la base online, ir a un estado anterior de la base, recuperar una base usando el ultimo backup full y aplicando los archives, etc.

Si el consumo de redo es importante, la I/O se va a ver comprometida y podría afectar el rendimiento general de la base. Recordar que la escritura en redo es secuencial, distinta a la escritura en datafiles. Siempre alojar los redo sobre raid 1 o similares y separados de los datafiles. Además, tambien tener en cuenta que con cada commit se debe escribir en redo en forma sincronica. Esto significa serializar, y por lo tanto debe ser lo mas eficiente posible.

Para que puedan medir el consumo de redo, y en consecuencia cantidad de archives generados, en sus bases, les paso una serie de metodos y consultas que uso habitualmente. Las consultas permiten obtener lo siguiente:

  • Consumo de Redo de la ultima hora
  • Consumo de Redo por sesión
  • Consumo de Redo por Consultas (*)

(*) El consumo por sentencia no se puede obtener en forma directa, entonces armé un procedimiento para ir guardando espacio por sqlid. Esta forma puede no ser muy precisa en ciertas ocasiones. Tiene que usarse teniendo ciertos requisitos y consideraciones. Puede ser muy util para testear el consumo de redo de una aplicación antes de la puesta en producción.

Para obtener consumo de redo global de la ultima hora (desde el ultimo snapshot de AWR) (redo consumption by DB)

Ejecutar la siguiente consulta que da el consumo de la base de datos desde el ultimo snapshot, es decir desde la ultima hora exacta. Es decir si lo ejecutamos a las 16:30, nos dará el consumo desde las 16hs para toda la base


select round((t1.value-t2.value)/1024/1024,2) "Consumo_Redo(MB)"
from
(select value from v$sysstat where name = 'redo size') t1,
(select value from dba_hist_sysstat
where stat_name = 'redo size'
and snap_id = (select max(snap_id) from dba_hist_sysstat)) t2


Para obtener consumo de redo por sesión (redo consumption by session)

Ejecutar la siguiente consulta, que da el consumo de redo por sesión. Considerar que el acumulado es desde que la sesión se abre

select se.INST_ID,
se.SID,
se.USERNAME,
se.OSUSER,
se.TERMINAL,
se.PROGRAM,
round(ss.value/1024/1024,2) "Redo Size(Mb)"
from gv$session se,
gv$sesstat ss,
v$statname sn
where se.SID = ss.SID
and ss.STATISTIC# = sn.STATISTIC#
and sn.NAME = 'redo size'
and username is not null
order by "Redo Size(Mb)" desc

Para obtener consumo de redo por sentencia (redo consumption by sqlid/sentence)

Primero realizar el setup copiado a continuación (copiar el contenido en un archivo .sql y ejecutarlo desde sqlplus en el usuario elegido como repositorio) :


Rem
Rem setup_redo_usage.sql (Redo Usage by Sentence/SQLID)
Rem
Rem NOMBRE
Rem setup_redo_usage.sql
Rem
Rem DESCRIPCION
Rem Configura la programacion de un job para recolectar
Rem información de uso de espacio de redo por sqlid.
Rem
Rem NOTAS - REQUISITOS DE INSTALACION
Rem El usuario que ejecute el script debera tener quota
Rem suficiente sobre un tablespace auxiliar (ej:TOOLS)
Rem para poder almacenar la informacion de monitoreo generada
Rem cada 5".
Rem Es necesario otorgar privilegios de SELECT sobre las
Rem vistas dinamicas:
Rem
Rem Conectado con sys hacer:
Rem
Rem grant select on gv_$sesstat to ;
Rem grant select on v_$statname to ;
Rem grant select on gv_$session to ;
Rem grant select on gv_$sqlarea to ;
Rem
Rem
Rem MODIFICADO (DD/MM/YY)
Rem
Rem
Rem Pablo A. Rovedo 06/01/11 -- Creado v 1.0
Rem

-- Borra la tabla TBL_REDO_USAGE si existe

drop table tmp$redo_usage
/

-- Crea la tabla de repositorio TBL_REDO_USAGE

create table tmp$redo_usage
(
USERNAME VARCHAR2(30),
OSUSER VARCHAR2(30),
TERMINAL VARCHAR2(30),
PROGRAM VARCHAR2(48),
SQL_ID VARCHAR2(13),
SQL_FULLTEXT CLOB,
REDO_SIZE NUMBER,
SNAP DATE,
INST_ID NUMBER(1)
)
pctfree 0
nologging
/


-- Crea el procedimiento que recolecta la información de Alocación de
-- espacio de redo

create or replace procedure p_get_redo_usage
is
begin
insert /*+ append */ into tmp$redo_usage
select s.USERNAME,
s.OSUSER,
s.terminal,
s.PROGRAM,
s.SQL_ID,
sa.SQL_FULLTEXT,
round(ss.VALUE/1024/1024) redo_size,
sysdate,
s.inst_id
from gv$sesstat ss,
v$statname sn,
gv$session s,
gv$sqlarea sa
where s.sid = ss.sid
and s.inst_id = ss.inst_id
and sn.STATISTIC# = ss.STATISTIC#
and s.sql_id = sa.SQL_ID
and s.inst_id = sa.inst_id
and sn.name = 'redo size'
and s.username not in ('SYS','SYSTEM');
commit;
end;
/

-- Borra el job si ya existe

begin
dbms_scheduler.drop_job(job_name => 'J_SAVE_REDO_USAGE',
force => true);
end;
/

-- Crea un nuevo job

begin
dbms_scheduler.create_job(
job_name => 'J_SAVE_REDO_USAGE'
,job_type => 'PLSQL_BLOCK'
,job_action => 'begin p_get_redo_usage; end;'
,start_date => sysdate
,end_date => sysdate+1/24 -- Recolecta durante 1 hora
,repeat_interval => 'FREQ=SECONDLY;BYSECOND=0,5,10,15,20,25,30,35,40,45,50,55'
,enabled => TRUE
,comments => 'Almacena Informacion de Alocación de espacio de redo');
end;
/

Luego de terminada la recolección (en el script se definió un intervalo
de monitoreo de 1 hora) se puede ejecutar la siguiente consulta para ver
los resultados:

select sql_id,sum(redo_size) "redo_size(Mb)"
from (select unique sql_id,
redo_size-lead(redo_size) over (partition by sql_id order by snap desc) redo_size
from tmp$redo_usage)
where redo_size is not null
group by sql_id
order by "redo_size(Mb)" desc

miércoles, 17 de noviembre de 2010

Reportes de Métricas de Carga y Tiempos de Respuesta de la Base de Datos (10g+)

A partir de 10g se agregaron vistas dinamicas e información historica para poder entender mejor y en forma mas rapida la actividad de la base de datos. Si bien los reportes de statspack y AWR tienen la información, estos se basan de los snapshots como referencia para analizar un intervalo. Generalmente los intervalos son de 1 hora (automatico y default en 10g+) y muchas veces hay que esperar al próximo snapshot para tener una idea de la actividad actual.

Con las nuevas vistas dinamicas se puede saber casi en tiempo real cual es la actividad de la base consultando las siguientes vistas dinamicas:

V$SYSMETRIC : Metricas mas recientes y menos recientes del ultimo minuto
(una muestra cada 15").
V$SYSMETRIC_HISTORY : Ultima hora de todas las muestras (elige una muestra por
minuto).
V$SYSMETRIC_SUMMARY : Resumen de la actividad de la ultima hora (maximos, minimos,
promedios y desviación standard).


y las vistas historias que persisten parte de la información de las vistas dinamicas
de la ultima hora (el proceso MMON se encarga de copiar parte de la información mas relevante de las vistas V$ a disco) y se externaliza el resultado con las siguientes vistas:

DBA_HIST_SYSMETRIC_HISTORY
DBA_HIST_SYSMETRIC_SUMMARY

Con esta información a disposición se obtiene una idea muy detallada de la actividad y el perfil de carga. Veamos una query que usa la vista sumarizada y retorna entre otros, los mismos datos que encontramos en los reportes statspack/awr en la parte "Load Profile" en la columa tabulada por segundo:

select metric_name,
case (metric_id)
when 2016 then round(minval/1024/1024,2)
when 2058 then round(minval/1024/1024,2)
else round(minval,2) end Min,
case (metric_id)
when 2016 then round(maxval/1024/1024,2)
when 2058 then round(maxval/1024/1024,2)
else round(maxval,2) end Max,
case (metric_id)
when 2016 then round(average/1024/1024,2)
when 2058 then round(average/1024/1024,2)
else round(average,2) end Avg,
case (metric_id)
when 2016 then round(standard_deviation/1024/1024,2)
when 2058 then round(standard_deviation/1024/1024,2)
else round(standard_deviation,2) end STDDEV,
case (metric_id)
when 2016 then 'Mbytes Per Second'
when 2058 then 'Mbytes Per Second'
else metric_unit end metric_unit
from v$sysmetric_summary
where metric_id in (2003,2026.2004,2006,2016,2018,2030,
2044,2046,2058,2071,2075,2081,2123)
order by metric_id



METRIC_NAME MIN MAX AVG STDDEV METRIC_UNIT
---------------------------------------------------------------- ---------- ---------- ---------- ---------- ----------------------------------------------------------------
User Transaction Per Sec 0 28.75 24.4 1.64 Transactions Per Second
Physical Writes Per Sec 0 29.63 21.91 2.25 Writes Per Second
Redo Generated Per Sec 0 .06 .05 0 Mbytes Per Second
Logons Per Sec 0 1.18 .93 .08 Logons Per Second
Logical Reads Per Sec 0 1015.72 592.05 75.19 Reads Per Second
Total Parse Count Per Sec 0 52.75 28.29 4.33 Parses Per Second
Hard Parse Count Per Sec 0 7.24 1.29 .88 Parses Per Second
Network Traffic Volume Per Sec 0 .03 .02 0 Mbytes Per Second
DB Block Changes Per Sec 0 339.75 287.39 19.15 Blocks Per Second
CPU Usage Per Sec 0 11.27 9.88 .51 CentiSeconds Per Second
User Rollback UndoRec Applied Per Sec 0 .3 .03 .07 Records Per Second
Database Time Per Sec 0 72.48 26.67 8.1 CentiSeconds Per Second



Los datos anteriores se pueden obtener por transacción si se llegara a necesitar.

Ahora voy a mostrar como obtener las metricas basadas en percentiles, con ratios y porcentajes de las ultimas dos muestras del ultimo minuto. La mas reciente es de a lo sumo 15 segundos y la mas antigua es de a lo sumo 60 segundos.


select metric_name,
round(value,2) value,
metric_unit
from v$sysmetric
where metric_name like '%\%%' escape '\'
or metric_name like '%Percent%'
or metric_name like '%Ratio%'


METRIC_NAME VALUE METRIC_UNIT
---------------------------------------------------------------- ---------- ----------------------------------------------------------------
Buffer Cache Hit Ratio 95.75 % (LogRead - PhyRead)/LogRead
Memory Sorts Ratio 100 % MemSort/(MemSort + DiskSort)
Redo Allocation Hit Ratio 100 % (#Redo - RedoSpaceReq)/#Redo
User Commits Percentage 100 % (UserCommit/TotalUserTxn)
User Rollbacks Percentage 0 % (UserRollback/TotalUserTxn)
Cursor Cache Hit Ratio 232.71 % CursorCacheHit/SoftParse
Execute Without Parse Ratio 63.74 % (ExecWOParse/TotalExec)
Soft Parse Ratio 96.05 % SoftParses/TotalParses
User Calls Ratio 33.48 % UserCalls/AllCalls
Host CPU Utilization (%) 4.63 % Busy/(Idle+Busy)
PX downgraded 1 to 25% Per Sec 0 PX Operations Per Second
PX downgraded 25 to 50% Per Sec 0 PX Operations Per Second
PX downgraded 50 to 75% Per Sec 0 PX Operations Per Second
PX downgraded 75 to 99% Per Sec 0 PX Operations Per Second
User Limit % 0 % Sessions/License_Limit
Database Wait Time Ratio 42.12 % Wait/DB_Time
Database CPU Time Ratio 57.88 % Cpu/DB_Time
Row Cache Hit Ratio 99.75 % Hits/Gets
Row Cache Miss Ratio .25 % Misses/Gets
Library Cache Hit Ratio 98.1 % Hits/Pins
Library Cache Miss Ratio 1.9 % Misses/Gets
Shared Pool Free % 91.27 % Free/Total
PGA Cache Hit % 99.89 % Bytes/TotalBytes
Process Limit % 24.7 % Processes/Limit
Session Limit % 16.86 % Sessions/Limit
Streams Pool Usage Percentage 0 % Memory allocated / Size of Streams pool
Buffer Cache Hit Ratio 96.18 % (LogRead - PhyRead)/LogRead
Memory Sorts Ratio 100 % MemSort/(MemSort + DiskSort)
Execute Without Parse Ratio 64.59 % (ExecWOParse/TotalExec)
Soft Parse Ratio 95.72 % SoftParses/TotalParses
Host CPU Utilization (%) 4.39 % Busy/(Idle+Busy)
Database CPU Time Ratio 15.8 % Cpu/DB_Time
Library Cache Hit Ratio 97.64 % Hits/Pins
Shared Pool Free % 91.28 % Free/Total


Otra consulta que suelo usar es mas simple y solo me retorna el tiempo de respuesta general y el tiempo de respuesta por transacción, ambos en segundos, y asi se puede analizar rapidamente y detectar si algo esta pasando con la base. Yo tengo idea de los tiempos razonables para cada base y si veo algo que se dispara me doy cuenta mirando solo esos dos valores. Abajo muestro como es la consulta que utilizo y la salida de la misma.

select end_time,
round(max(decode(metric_id,2106,value/100,null)),4) "SQLRTime",
round(max(decode(metric_id,2109,value/100,null)),4) "RTime/Trx"
from v$sysmetric_history
where metric_id in (2106,2109)
and end_time > sysdate-10/24/60
group by end_time
order by end_time desc


END_TIME SQLRTime RTime/Trx
18/11/2010 12:15:34 p.m. 0.0013 0.0172
18/11/2010 12:14:33 p.m. 0.0063 0.0843
18/11/2010 12:13:33 p.m. 0.0087 0.1101
18/11/2010 12:12:33 p.m. 0.0039 0.1147
18/11/2010 12:11:34 p.m. 0.009 0.1214
18/11/2010 12:10:34 p.m. 0.0062 0.1145
18/11/2010 12:09:34 p.m. 0.0079 0.1102
18/11/2010 12:08:34 p.m. 0.0081 0.1167
18/11/2010 12:07:34 p.m. 0.0085 0.1112
18/11/2010 12:06:34 p.m. 0.0078 0.1141


Si quisiera ver la historia mas antigua o necesito armar un reporte historico sumarizado y/o agrupado por hora, dia, semana o mes se puede usar una vista historica (DBA_HIST_xxx).

viernes, 26 de febrero de 2010

Reporte para obtener métricas de SO desde AWR

Como podemos hace para detectar problemas de cpu o i/o sin tener que pedir ayuda al grupo de Sistemas Operativos?, suena complicado, no?. Bueno... a partir de 10g una opción, por lo menos para ir teniendo un primer panorama, es ver si esta pasando algo con la caja donde reside nuestra base de datos consultando en el repositorio AWR. Para exteriorizar estas métricas existe la vista historica: DBA_HIST_OSSTAT, que en 10g contiene lo siguiente:

STAT_NAME
-------------------------------------------
BUSY_TIME
AVG_IDLE_TIME
NUM_CPUS
AVG_BUSY_TIME
OS_CPU_WAIT_TIME
VM_IN_BYTES
AVG_USER_TIME
AVG_SYS_TIME
LOAD
SYS_TIME
RSRC_MGR_CPU_WAIT_TIME
IDLE_TIME
USER_TIME
PHYSICAL_MEMORY_BYTES
IOWAIT_TIME
AVG_IOWAIT_TIME
VM_OUT_BYTES

y en 11g R1 se agregan las siguientes:

TCP_SEND_SIZE_DEFAULT
TCP_RECEIVE_SIZE_DEFAULT
TCP_RECEIVE_SIZE_MAX
NUM_CPU_SOCKETS
TCP_SEND_SIZE_MAX
NUM_CPU_CORES


Abajo, les copio un script que armé para ver algunas métricas importantes. El reporte toma como unico parametro la cantidad de horas hacia atras que quiero analizar.


set line 120
set pagesize 9999
set verify off
accept horas prompt "Ingrese cantidad de horas hacia atras que desea reportar: "

col snap format a20

select unique to_char(snap,'DD-MON-YYYY HH24') snap,
avg_idle_time-lead(avg_idle_time) over (partition by st order by snap desc) avg_idle_time,
avg_user_time-lead(avg_user_time) over (partition by st order by snap desc) avg_user_time,
avg_sys_time-lead(avg_sys_time) over (partition by st order by snap desc) avg_sys_time,
avg_iowait_time-lead(avg_iowait_time) over (partition by st order by snap desc) avg_iowait_time,
os_cpu_wait_time-lead(os_cpu_wait_time) over (partition by st order by snap desc) os_cpu_wait_time
from
(select s.end_interval_time snap,
s.startup_time st,
max(decode(stat_name,'AVG_IDLE_TIME',value,null)) AVG_IDLE_TIME,
max(decode(stat_name,'AVG_USER_TIME',value,null)) AVG_USER_TIME,
max(decode(stat_name,'AVG_SYS_TIME',value,null)) AVG_SYS_TIME,
max(decode(stat_name,'AVG_IOWAIT_TIME',value,null)) AVG_IOWAIT_TIME,
max(decode(stat_name,'OS_CPU_WAIT_TIME',value,null)) OS_CPU_WAIT_TIME
from dba_hist_osstat os,
dba_hist_snapshot s
where s.snap_id = os.snap_id
group by s.end_interval_time,s.startup_time)
where snap > sysdate-&horas/24
order by snap desc
/


SNAP AVG_IDLE_TIME AVG_USER_TIME AVG_SYS_TIME AVG_IOWAIT_TIME OS_CPU_WAIT_TIME
25-FEB-10 09 211854 108399 40156 99439 2087400
25-FEB-10 08 271995 61131 27296 97923 1199800
25-FEB-10 07 236760 85951 31938 84592 1489500
25-FEB-10 06 180172 130112 50168 90475 2281500
25-FEB-10 05 182995 123247 54623 105254 2274500
25-FEB-10 04 193166 116204 51902 127028 2197000
25-FEB-10 03 195871 119849 45241 130806 2126400
25-FEB-10 02 236610 85916 37782 160617 1574500


Obviamente, para tener un diagnostico preciso de uso de recursos de SO lo ideal es pedir a los grupos encargados de la administración del SO, networking o storage reportes detallados historicos de actividad, pero si queremos tener una primera foto en forma rapida de lo que esta pasando con solo tener acceso al repositorio de la base, podemos usar la consulta de arriba o cualquier variación (se puede agregar columnas para reportar uso de memoria virtual, tráfico de red, etc) de esta para tener una primera impresión.

viernes, 29 de enero de 2010

Enfoque para medir la disponibilidad efectiva de una base de datos Oracle

La tarea de los dba's es poco conocida y a veces mal entendida por la mayoria de la gente que no esta especificamente en el tema de base de datos. Es por eso que muchas veces es dificil estimar o medir nuestra eficiencia si no se conoce nada o poco del tema. Dado que la gestión y mantenimiento de bases de datos es un tema muy técnico, resulta ser todo un misterio, incluso para algunos gerentes de sistemas con perfil más de gestión que técnico, que se conforman con no tener sobresaltos y que las bases productivas esten disponibles y con un tiempo de respuesta decente, obviamente esto es deseable en cualquier ambito, pero no permite medir eficiencia con detalle.

Una forma de medir nuestra eficiencia con una métrica entendible por el público en general, podría ser mostrar en numeros la disponibilidad de las bases de datos que gestionamos. Con disponibilidad (availability) me refiero al tiempo total que las bases están arriba, es decir que se pueden usar. Esto en alguna medida (al margen de temas de rendimiento) podría mostrar o cuantificar cuan bien administramos ya que si la base esta arriba las aplicaciones que usan dicha base están operativas. Quien no escucho alguna vez en la cola de un banco o realizando algún trámite municipal la frase: "el sistema esta caido, vuelva mas tarde..."?, en la gran mayoria de los casos es por algun problema en la base de datos o bien porque se tuvieron que realizar tareas de mantenimiento de urgencia, por mala administración, por errores de los operadores o dba's, etc. Es cierto que las bases de datos, y sobre todo Oracle, hoy en día son muy robustas y se han minimizado muchos de los potenciales problemas. Tambien el hardware es mas confiable y, si se tiene presupuesto, se puede redundar en componentes lo cual hace mucho mas improbable una indisponibilidad.

Para poder medir el tiempo de disponibilidad total de una base de datos armé un script que reporta la disponibilidad de los ultimos n dias. En el ejemplo lo hice para los ultimos 4 meses (120 dias). Hay que tomar en cuenta que el reporte se basa de ciertas premisas:

  • Debe estar disponible la data a procesar en el alert de la base a analizar. Si se realiza alguna tarea de depuracion diaria, semanal, etc, se deberá concatenar de antemano en un archivo toda la info grabada en un archivo de alert que contenga la totalidad de la información necesaria.
  • Si no existen registros con información de fecha y hora entre dos startups se toma como que la base estuvo arriba todo ese tiempo.
  • Cuando la base se baja abruptamente, ya sea por un shutdown abort, por un error grave de HW o bien porque se desconectó el cable de alimentación no se registra el shutdown en el alert por lo cual se pierde esa referencia y se usará el último registro disponible en el alert.


Para armar poder correr el reporte primero hay que generar una tabla externa (recordemos que esto existe a partir de 9i por lo cual este metodo no funcionara en versiones anteriores) que permite leer con la sentencia sql el archivo alert.


create directory BDUMP as '/u01/app/oracle/admin/ROP102/bdump'

CREATE TABLE "ALERT_LOG"
( "TEXT" VARCHAR2(400)
)
ORGANIZATION EXTERNAL
( TYPE ORACLE_LOADER
DEFAULT DIRECTORY "BDUMP"
ACCESS PARAMETERS
( records delimited by newline
nobadfile
nodiscardfile
nologfile
)
LOCATION
( 'alert_ROP102.log'
)
)
REJECT LIMIT UNLIMITED;


Una vez creada la tabla externa ya puedo ejecutar la sentencia. La lógica que tomé se basa en buscar el string en el alert que registra el startup de la base y luego (usando funciones análiticas) obtener el registro próximo anterior. Con esto infiero que el tiempo entre el ultimo registro anterior al startup y el registro de startup es el tiempo en el que estuvo baja la base. Esto es una aproximación y puede tener cierta imprecisión pero creo que se acerca bastante a la realidad en la mayoria de los casos. Existen registros en el catalogo de Oracle de cuando una base levanta, pero no se tiene información de cuando se baja, incluso consideremos que si existiera esto, seria complicado registrar los shutdown abort. Por ese motivo recalco que el script que realicé es una aproximación, dado que no se puede saber con exactitud el momento preciso del en el que la base se bajó. Considerando todo lo expuesto anteriormente ahora si vemos como funciona el reporte:


select min_date,
max_date,
round((max_date-min_date)*24,2) total,
down,
round(100-((down*100)/((max_date-min_date)*24)),3) pct_up
from
(select min(to_date(last_time,'Dy Mon DD HH24:MI:SS YYYY','NLS_DATE_LANGUAGE=english')) min_date,
max(to_date(start_time,'Dy Mon DD HH24:MI:SS YYYY','NLS_DATE_LANGUAGE=english')) max_date,
sum(round((to_date(start_time,'Dy Mon DD HH24:MI:SS YYYY','NLS_DATE_LANGUAGE=english')-
to_date(last_time,'Dy Mon DD HH24:MI:SS YYYY','NLS_DATE_LANGUAGE=english'))*24,2)) down
from
(select start_time,
last_time
from
(select text,
lag(text,1) over (order by r) start_time,
lag(text,2) over (order by r) last_time
from ( select rownum r, text
from alert_log
where text like '___ ___ __ __:__:__ 20__'
or text like 'Starting ORACLE instance (normal)'))
where text like 'Starting ORACLE instance (normal)'
and last_time like '___ ___ __ __:__:__ 20__'
and to_date(start_time,'Dy Mon DD HH24:MI:SS YYYY','NLS_DATE_LANGUAGE=english') > sysdate-120))


MIN_DATE MAX_DATE TOTAL DOWN PCT_UP
-------- -------- ------ ---- ------
02/10/2009 29/01/2010 2858.22 22.45 99,214


Como se ve en la salida se muestra el intervalo de fechas analizadas, el tiempo total transcurrido entre las dos fechas (medido en horas), el tiempo que estuvo baja (medido en horas) y el porcentaje que estuvo arriba la base. Como vemos esta en valores esperados ya que la disponibilidad esta por arriba del 99%.

viernes, 11 de diciembre de 2009

Reporte para analizar planes que referencien a una tabla dada

Cada tanto me consultan desde el area de desarrollo sobre si agregando tal o cual indice a una tabla mejoraria el rendimiento de la aplicación. Como en muchas ocasiones yo no conozco el negocio me resulta dificil saber como se usa la tabla, es decir, que sentencias la referencian, si se hacen updates, deletes, inserts o solo se consulta. Para poder analizar esto a veces uso el script que copio abajo, que da el plan de ejecución y la ultima vez que se ejecutaron las sentencias que referencian a una cierta tabla. A esto se le puede sumar la generación de sugerencias usando advisor tales como SQL Tuning y SQL Access Advisors.


set serverout on
set line 120
set pagesize 9999
set verify off
set feed off

ACCEPT tabla PROMPT "Ingrese Tabla a Analizar: "
PROMPT
PROMPT

begin
for i in (select sp.sql_id,max(sh.begin_interval_time) ufecha
from dba_hist_sql_plan sp,
dba_hist_sqlstat ss,
dba_hist_snapshot sh
where sp.sql_id = ss.sql_id
and ss.snap_id = sh.snap_id
and sp.object_name = upper('&tabla')
group by sp.sql_id)
loop
dbms_output.put_line('Ultima Ejecución: '||to_char(i.ufecha,'DD/MM/YYYY HH24:MI'));
for j in (select * from table(dbms_xplan.display_awr(i.sql_id)))
loop
dbms_output.put_line (j.plan_table_output);
end loop;
dbms_output.put_line(chr(10)||rpad('*',100,'*')||chr(10));
end loop;
end;
/

set verify on
set feed on

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, 22 de julio de 2009

Reporte de sentencias que cambiaron su plan durante un cierto periodo de tiempo

Una de las posibles causas de baja de rendimiento de una base puede deberse a que una sentencia central haya cambiado su plan de ejecución. Es importante tener monitoreadas las sentencias mas importantes de manera de poder anticipar problemas de performance generalizado. Para poder observar los cambios de planes les paso el codigo de un script que armé que además de detectar los sqlid's que tienen más de un plan durante un cierto periodo, también genera el plan para poder chequerlo mas rapidamente


set term off
set serveroutput on size unlimited
set pagesize 4000
set echo off
set verify off
set feedback off
set line 1000
alter session set nls_date_format='DD/MM/YYYY HH24:MI:SS';
set term on

prompt *****************************************************************
prompt * Ingrese la cantidad de dias para analizar el cambio de planes *
prompt *****************************************************************
accept dias prompt 'Cantidad de Dias > '
prompt ***************************************************************************
prompt * Ingrese el nombre del archivo y ruta donde desea guardar la información *
prompt ***************************************************************************
accept path prompt 'Path > '

set term off;

spool '&path'


prompt
prompt ***************************************************************
prompt ************* Listado de Consultas y Planes *****************
prompt ***************************************************************
prompt


-- Query para detectar cambios de planes
select a.sql_id,a.timestamp,a.plan_hash_value,a.optimizer,a.cost
from dba_hist_sql_plan a
where a.sql_id in (select b.sql_id
from dba_hist_sql_plan b
where timestamp > trunc(sysdate-&dias)
group by sql_id
having count(distinct b.plan_hash_value) > 1)
and a.timestamp > trunc(sysdate-&dias)
and a.id = 0
order by 1,2;

prompt
prompt
prompt ***************************************************************
prompt ************* Detalle de Consultas y Planes *****************
prompt ***************************************************************
prompt


begin
for i in (select a.sql_id,a.timestamp,a.plan_hash_value,a.optimizer,a.cost
from dba_hist_sql_plan a
where a.sql_id in (select b.sql_id
from dba_hist_sql_plan b
where timestamp > trunc(sysdate-&dias)
group by sql_id
having count(distinct b.plan_hash_value) > 1)
and a.timestamp > trunc(sysdate-&dias)
and a.id = 0
order by 1,2)
loop
for j in (select plan_table_output
from table(dbms_xplan.display_awr(i.sql_id,i.plan_hash_value)))
loop
dbms_output.put_line(j.plan_table_output);
end loop;
end loop;
end;
/

set term on;
prompt Archivo de Salida del reporte --> "&path"
set term off;
set echo on
set verify on
set feedback on
set term on

miércoles, 24 de junio de 2009

Chequeo General de la "Salud" de la base de Datos (Oracle Database Health)

El script que copié abajo permite tener una primera idea del estado de la base de datos. Se puede ejecutar para realizar un reporte de los objetos que componen la base. Revisa alocaciones, invalidaciones, definición de segmentos no default, db_link
no accesibles, etc.



Rem DB Health (by Pablo A. Rovedo)
Rem ---------------------------------------------------
Rem Created : 2007.09.13
Rem Function : Script para chequear el estado general de una base
Rem de datos (Infraestructura)
Rem
Rem
Rem Modificated: 2008.07.16 - Se agreron puntos 28,29, 30 y 31
Rem Modificated: 2008.06.24 - Se agregaron puntos 15,23..27 Rovedo P.
Rem Modificated: 2008.06.23 - Se agregaron puntos 13,14 y 16 Rovedo P.
Rem Modificated: 2007.09.24 - Se agregaron puntos 17..23 Rovedo P.
Rem Modificated: 2008.07.30 - Se agregaron puntos 24..31 Rovedo P.
Rem



Rem 1 - Usuarios con tablespace SYSTEM como temporary tablespace (exceptuando users SYS y SYSTEM)
Rem ---------------------------------------------------------------------------------------------

select username,default_tablespace
from dba_users
where default_tablespace = 'SYSTEM'
and username not in ('SYS','SYSTEM')
/


Rem 2 - Usuarios con tablespace SYSTEM como default tablespace (exceptuando users SYS y SYSTEM)
Rem -----------------------------------------------------------------------------------------

select username,temporary_tablespace
from dba_users
where temporary_tablespace = 'SYSTEM'
and username not in ('SYS','SYSTEM')
/


Rem 3 - Indices UNUSABLES
Rem ---------------------

select owner,count(1)
from dba_indexes
where status = 'INVALID'
group by owner
/

select owner,
index_name,
index_type,
table_owner,
table_name,
table_type,
tablespace_name
from dba_indexes
where status = 'INVALID'
/


Rem 4 - Objetos invalidos
Rem ---------------------

select owner,object_type,count(1)
from dba_objects
where status = 'INVALID'
group by owner,object_type
order by 1,2
/

select owner,
object_name,
created,
last_ddl_time
from dba_objects
where status = 'INVALID'
order by 1,2
/


Rem 5 - Paquetes con bodies sin que no tengan sus correspondientes headers
Rem ----------------------------------------------------------------------

select unique owner,name
from dba_source a
where type = 'PACKAGE BODY'
and not exists (select null
from dba_source b
where a.owner = b.owner
and a.name = b.name
and b.type = 'PACKAGE')
/


Rem 6 - Constraints deshabilitadas
Rem ------------------------------

select owner,
case constraint_type
when 'P' then 'PRIMARY_KEY'
when 'R' then 'FOREIGN_KEY'
when 'U' then 'UNIQUE'
when 'C' then 'CHECK'
end constraint_type,
count(1)
from dba_constraints
where status = 'DISABLED'
group by owner,constraint_type
order by 1,2
/


select owner,
constraint_name,
constraint_type,
table_name
from dba_constraints
where status = 'DISABLED'
order by 1,2
/


Rem 7 - Triggers deshabilitados
Rem ---------------------------

select owner,
trigger_name,
trigger_type,
triggering_event,
table_owner,
table_name
from dba_triggers
where status = 'DISABLED'
order by 1,2
/


Rem 8 - Controlar que sys.aud$ no este en tablespace SYSTEM
Rem -------------------------------------------------------

select name,
value,
display_value,
description
from v$parameter
where name like 'audit%'
/

select owner,
segment_name,
tablespace_name
from dba_segments
where segment_name = 'AUD$'
/


Rem 9 - Jobs en estado broken
Rem -------------------------

select * from dba_jobs
where broken = 'Y'
/


Rem 10 - Jobs con next_date menor a sysdate
Rem ---------------------------------------

select *
from dba_jobs
where sysdate > next_date
/


Rem 11 - Jobs con fallas
------------------------

select *
from dba_jobs
where failures > 0
/


Rem 12 - Roles no otorgados a ningun rol o user
Rem -------------------------------------------

select role from dba_roles
minus
select granted_role from dba_role_privs
/

Rem 13 - Sinonimos publicos que apuntan a objetos inexistentes
Rem ----------------------------------------------------------

select * from dba_synonyms a
where owner = 'PUBLIC'
and not exists (select null
from dba_objects b
where a.table_owner = b.owner
and a.table_name = b.object_name)
/


Rem 14 - Sinonimos privados que apuntan a objetos inexistentes
Rem ----------------------------------------------------------

select * from dba_synonyms a
where owner != 'PUBLIC'
and not exists (select null
from dba_objects b
where a.table_owner = b.owner
and a.table_name = b.object_name)
/

Rem 15 - Database links que son inaccesibles
Rem ----------------------------------------

begin
for i in (select decode(owner,'PUBLIC',user,owner) owner,db_link from dba_db_links)
loop
begin
execute immediate 'create view 'i.owner'.TEST as select count(1) c from dual@'i.db_link;
dbms_output.put_line('OWNER: 'i.owner'; DB_LINK: 'i.db_link' --> ACCESIBLE');
execute immediate 'drop view 'i.owner'.TEST';
exception
when others then
dbms_output.put_line('OWNER: 'i.owner'; DB_LINK: 'i.db_link' --> NO ACCESIBLE');
end;
end loop;
end;
/

Rem 16 - Segmentos con mas de 100 extents (excluir sys y system)
Rem ----------------------------------------------------------------

select * from dba_segments
where extents > 100
and owner not in ('SYS','SYSTEM')
/


Rem 17 - Tablas no analizadas con mas de 1000 registros
-------------------------------------------------------

set serverout on size 500000
declare
l_cnt int;
begin
for i in (select * from dba_tables
where last_analyzed is null
and owner not in ('SYS','SYSTEM'))
loop
execute immediate 'select count(1) from 'i.owner'.'i.table_name
into l_cnt;
if (l_cnt > 1000) then
dbms_output.put_line(i.owner'.'i.table_name);
end if;
end loop;
end;
/


Rem 18 - Tablas con mas del 1% de chained rows
Rem ------------------------------------------

select owner,table_name,num_rows,chain_cnt from dba_tables
where owner not in ('SYS','SYSTEM')
and chain_cnt/num_rows > 0.01
and num_rows > 0
/


Rem 19 - Tablas con mas de 5 indices
Rem --------------------------------

select owner,table_name,count(1)
from dba_indexes
where owner not in ('SYS','SYSTEM')
group by owner,table_name
having count(1) > 5
/


Rem 20 - Tablas con indices superfluos
Rem ----------------------------------

select a.index_name '(' a.cols ')' cols,
b.index_name '(' b.cols ')' cols
from (select index_name, table_name,
rtrim(
max(decode(column_position,1,column_name,null)) ','
max(decode(column_position,2,column_name,null)) ','
max(decode(column_position,3,column_name,null)) ','
max(decode(column_position,4,column_name,null)) ','
max(decode(column_position,5,column_name,null)) ','
max(decode(column_position,6,column_name,null)) ','
max(decode(column_position,7,column_name,null)) ','
max(decode(column_position,8,column_name,null)) ','
max(decode(column_position,9,column_name,null)) ','
max(decode(column_position,10,column_name,null)) , ',' ) cols
from user_ind_columns
group by table_name, index_name ) a,
(select index_name, table_name,
rtrim(
max(decode(column_position,1,column_name,null)) ','
max(decode(column_position,2,column_name,null)) ','
max(decode(column_position,3,column_name,null)) ','
max(decode(column_position,4,column_name,null)) ','
max(decode(column_position,5,column_name,null)) ','
max(decode(column_position,6,column_name,null)) ','
max(decode(column_position,7,column_name,null)) ','
max(decode(column_position,8,column_name,null)) ','
max(decode(column_position,9,column_name,null)) ','
max(decode(column_position,10,column_name,null)) , ',' ) cols
from user_ind_columns
group by table_name, index_name ) b
where a.table_name = b.table_name
and a.index_name <> b.index_name
and a.cols like b.cols '%'
/


Rem 21 - Tablas que no poseean primary key y que no esten vacias
Rem ------------------------------------------------------------

select owner,table_name
from dba_tables a
where owner not in ('SYS','SYSTEM')
and num_rows > 0
and not exists (select null
from dba_constraints b
where a.owner = b.owner
and a.table_name = b.table_name
and b.constraint_type = 'P')
order by 1,2
/


Rem 22 - Tablas que no tengan los parametros de storage defaults
Rem ------------------------------------------------------------


select a.owner,a.table_name,a.pct_increase,a.initial_extent,a.next_extent,
a.max_extents,a.min_extents,b.tablespace_name
from dba_tables a,
dba_tablespaces b
where a.tablespace_name = b.tablespace_name
and a.owner not in ('SYS','SYSTEM')
and (a.pct_increase != b.pct_increase
or a.initial_extent != b.initial_extent
or a.next_extent != b.next_extent
or a.max_extents != b.max_extents
or a.min_extents != b.min_extents)
/


Rem 23 - Tablas con valores pct_free o pct_used no defaults
Rem -------------------------------------------------------


select owner,table_name
from dba_tables
where owner not in ('SYS','SYSTEM')
and (pct_free != 10
or pct_used != 40)
/


Rem 24 - Indices no analizados
Rem --------------------------

select owner,index_name,table_name
from dba_indexes
where owner not in ('SYS','SYSTEM')
and last_analyzed is null


Rem 25 - Foreign keys sin indices asociados (Analizar a nivel usuario)
Rem ---------------------------------------


select decode( b.table_name, NULL, '****', 'ok' ) Status,
a.table_name, a.columns, b.columns
from
( select substr(a.table_name,1,30) table_name,
substr(a.constraint_name,1,30) constraint_name,
max(decode(position, 1, substr(column_name,1,30),NULL))
max(decode(position, 2,', 'substr(column_name,1,30),NULL))
max(decode(position, 3,', 'substr(column_name,1,30),NULL))
max(decode(position, 4,', 'substr(column_name,1,30),NULL))
max(decode(position, 5,', 'substr(column_name,1,30),NULL))
max(decode(position, 6,', 'substr(column_name,1,30),NULL))
max(decode(position, 7,', 'substr(column_name,1,30),NULL))
max(decode(position, 8,', 'substr(column_name,1,30),NULL))
max(decode(position, 9,', 'substr(column_name,1,30),NULL))
max(decode(position,10,', 'substr(column_name,1,30),NULL))
max(decode(position,11,', 'substr(column_name,1,30),NULL))
max(decode(position,12,', 'substr(column_name,1,30),NULL))
max(decode(position,13,', 'substr(column_name,1,30),NULL))
max(decode(position,14,', 'substr(column_name,1,30),NULL))
max(decode(position,15,', 'substr(column_name,1,30),NULL))
max(decode(position,16,', 'substr(column_name,1,30),NULL)) columns
from user_cons_columns a, user_constraints b
where a.constraint_name = b.constraint_name
and b.constraint_type = 'R'
group by substr(a.table_name,1,30), substr(a.constraint_name,1,30) ) a,
( select substr(table_name,1,30) table_name, substr(index_name,1,30) index_name,
max(decode(column_position, 1, substr(column_name,1,30),NULL))
max(decode(column_position, 2,', 'substr(column_name,1,30),NULL))
max(decode(column_position, 3,', 'substr(column_name,1,30),NULL))
max(decode(column_position, 4,', 'substr(column_name,1,30),NULL))
max(decode(column_position, 5,', 'substr(column_name,1,30),NULL))
max(decode(column_position, 6,', 'substr(column_name,1,30),NULL))
max(decode(column_position, 7,', 'substr(column_name,1,30),NULL))
max(decode(column_position, 8,', 'substr(column_name,1,30),NULL))
max(decode(column_position, 9,', 'substr(column_name,1,30),NULL))
max(decode(column_position,10,', 'substr(column_name,1,30),NULL))
max(decode(column_position,11,', 'substr(column_name,1,30),NULL))
max(decode(column_position,12,', 'substr(column_name,1,30),NULL))
max(decode(column_position,13,', 'substr(column_name,1,30),NULL))
max(decode(column_position,14,', 'substr(column_name,1,30),NULL))
max(decode(column_position,15,', 'substr(column_name,1,30),NULL))
max(decode(column_position,16,', 'substr(column_name,1,30),NULL)) columns
from user_ind_columns
group by substr(table_name,1,30), substr(index_name,1,30) ) b
where a.table_name = b.table_name (+)
and b.columns (+) like a.columns '%'


Rem 26 - Tablespaces con Manejo de Extents por Diccionario
Rem ------------------------------------------------------

select tablespace_name
from dba_tablespaces
where extent_management = 'DICTIONARY'
/


Rem 27 - Tablespaces con Manejo de Segmentos Manual
Rem ------------------------------------------------------

select tablespace_name
from dba_tablespaces
where segment_space_management = 'MANUAL'
and contents = 'PERMANENT'
/

Rem 28 - Tablas con mas de 100 columnas
Rem -------------------------------------------------------------------

select owner,table_name,count(1)
from dba_tab_columns
where owner not in ('SYS','SYSTEM')
group by owner,table_name
having count(1) > 100
/

Rem 29 - Owners que comparten tablespaces
Rem ------------------------------------------------------------------

select a.owner,b.owner,
a.tablespace_name,
a.cantseg,
b.cantseg
from (select owner,tablespace_name,count(1) cantseg
from dba_segments
where owner not in ('SYS','SYSTEM','SYSMAN','OUTLN','DBSNMP','SYSAUX')
group by owner,tablespace_name) a,
(select owner,tablespace_name,count(1) cantseg
from dba_segments
where owner not in ('SYS','SYSTEM','SYSMAN','OUTLN','DBSNMP','SYSAUX')
group by owner,tablespace_name) b
where a.tablespace_name = b.tablespace_name
and a.owner != b.owner
order by a.cantseg+b.cantseg desc
/

Rem 30 -Tablas Candidatas para Particionar
Rem ------------------------------------------------------------------

select unique a.object_owner,a.object_name
from dba_hist_sql_plan a,
dba_tab_statistics b
where a.object_owner = b.owner
and a.object_name = b.table_name
and a.options = 'FULL'
and b.num_rows > 10000000
/

Rem 31 - Espacio libre y ocupado en Tablespaces
Rem ------------------------------------------------------------------

column "Name" format a40
column tablespace format a10

SELECT d.status "Status", d.tablespace_name "Name", d.contents "Type",
TO_CHAR(NVL(nvl(a.bytes,b.bytes) / 1024 / 1024, 0),'99G999G990D900') "Size (M)",
TO_CHAR(NVL(nvl(a.bytes,b.bytes) - NVL(f.bytes, 0),0)/1024/1024, '99G999G990D900') "Used (M)",
TO_CHAR(NVL((nvl(a.bytes,b.bytes) - NVL(f.bytes, 0)) / nvl(a.bytes,b.bytes) * 100, 0), '990D00') "Used %"
FROM sys.dba_tablespaces d,
(select tablespace_name, sum(bytes) bytes
from dba_data_files group by tablespace_name) a,
(select tablespace_name, sum(bytes) bytes
from dba_temp_files group by tablespace_name) b,
(select tablespace_name, sum(bytes) bytes
from dba_free_space group by tablespace_name) f
WHERE d.tablespace_name = a.tablespace_name(+) AND
d.tablespace_name = b.tablespace_name(+) AND
d.tablespace_name = f.tablespace_name(+)
/

jueves, 14 de mayo de 2009

Script para listar las sentencias ejecutadas durante un periodo (ordenadas por elapsed time)

El siguiente script genera un listado ordenado por tiempo de ejecución de todas las sentencias ejecutadas en un cierto periodo de tiempo. La información se optiene del repositorio de AWR. Ademas se detallan los planes de ejecución de cada sentencia


-- -----------------------------------------------------------------------------------
-- Nombre : top_sqls.sql
-- Autor : Pablo A. Rovedo
-- Descripción : Lista las sentencias ejecutadas durante un periodo de tiempo
-- ordenadas por elapsed_time
-- -----------------------------------------------------------------------------------

set serverout on size unlimited
set pagesize 9999
set linesize 250

prompt *************************************************
prompt * Ingrese Fecha Inicial AWR [dd/mm/aaaa hh24:mi]*
prompt *************************************************
accept fecha1 prompt '> '

prompt *************************************************
prompt * Ingrese Fecha Final AWR [dd/mm/aaaa hh24.mi] *
prompt *************************************************
accept fecha2 prompt '> '

prompt ************************************************
prompt * Ingrese el Path para guardar la información *
prompt ************************************************
accept path prompt 'Path del reporte > '

set term off
spool &path


begin

dbms_output.put_line('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~');
dbms_output.put_line('~~~~~~~~~~~~~~~~~ Reporte de Consultas SQL y Plan de Ejecución ~~~~~~~~~~~~~~~~~');
dbms_output.put_line('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~');
dbms_output.put_line('-----------------------------------------------------------------');
dbms_output.put_line(' Sentencias ordenadas por ELAPSED TIME');
dbms_output.put_line('-----------------------------------------------------------------');
for i in ( select ss.sql_id,
ss.plan_hash_value,
max(ss.fetches_total) "fetch_total",
max(ss.executions_total) "exec_total",
max(ss.rows_processed_total) "rows_proc",
round(max(ss.elapsed_time_total)/1000000,2) "ela_time"
from dba_hist_sqlstat ss,
dba_hist_snapshot s
where s.snap_id = ss.snap_id
and parsing_schema_name = 'CR'
and s.begin_interval_time between to_date('&fecha1','DD/MM/YYYY HH24:MI') and
to_date('&fecha2','DD/MM/YYYY HH24:MI')
group by ss.sql_id,ss.plan_hash_value
order by "ela_time" desc)
loop
dbms_output.put_line(' ');
dbms_output.put_line(' ');
dbms_output.put_line('===================================================================');
dbms_output.put_line('SQL ID: '|| i.sql_id || ' Elapsed Time(s): ' ||i."ela_time");
dbms_output.put_line('===================================================================');

for j in (select plan_table_output
from table(dbms_xplan.display_awr(i.sql_id,i.plan_hash_value)))
loop
dbms_output.put_line(j.plan_table_output);
end loop;
end loop;
end;
/
set term on;
prompt Ejecutando Proceso...
set term off;


set term on;
prompt Fin Proceso!
set term off;
spool off;
set echo on
set verify on
set term on
prompt Archivo de Salida del reporte --> &path

miércoles, 8 de abril de 2009

Monitorear el estado de las estadisticas de los segmentos referenciados en una sesión

Cualquiera que haya trabajado con bases de datos Oracle habrá experimentado alguna vez problemas de performance. La gran mayoria de estas degradaciones en los tiempos de los procesos o aplicaciones se deben a falta o desactualización de las estadísticas de los segmentos referenciados en las sentencias ejecutadas. Para los que cada tanto tenemos la tarea de analizar problemas de rendimiento, nos resulta necesario partir por identificar la sesión o sesiones instanciadas por el proceso o aplicacion con el problema. En el caso de procesos batch los operadores nos suelen suministrar los sid de las sesiones para poder generar una traza de ejecución o monitoreo online. El siguiente script muestra los segmentos con estadísticas STALE o EMPTY dado un sid de sesión:



set line 150
set pagesize 9999
set verify off

col owner format a15
col segment_name format a30
col type format a5

ACCEPT sid PROMPT "Ingrese el SID a analizar: "


select sq.sql_id,
sq.object_type type,
st.owner owner,
st.table_name segment_name,
st.partition_name partition_name ,
st.subpartition_name subpartition_name,
st.last_analyzed last_analyzed
from v$session se,
v$sql_plan sq,
dba_tab_statistics st
where se.sql_id = sq.sql_id
and se.sid = &sid
and sq.OBJECT_OWNER = st.owner
and sq.OBJECT_NAME = st.table_name
and sq.object_type = 'TABLE'
and sq.object_owner not in ('SYS','SYSTEM')
and nvl(st.stale_stats,'YES') = 'YES'
union all
select sq.sql_id,
sq.object_type type,
si.owner owner,
si.table_name segment_name,
si.partition_name partition_name ,
si.subpartition_name subpartition_name,
si.last_analyzed last_analyzed
from v$session se,
v$sql_plan sq,
dba_ind_statistics si
where se.sql_id = sq.sql_id
and se.sid = &sid
and sq.OBJECT_OWNER = si.owner
and sq.OBJECT_NAME = si.index_name
and sq.object_type = 'INDEX'
and sq.object_owner not in ('SYS','SYSTEM')
and nvl(si.stale_stats,'YES') = 'YES'
/

set verify on


Si quisieramos ver todas los segmentos con estadisticas nulas o viejas de todas las tablas referenciadas actualmente podemos usar la siguiente variante mas generica del script anterior:


set line 150
set pagesize 9999

col owner format a15
col segment_name format a30
col type format a5

select sq.sql_id,
sq.object_type type,
st.owner owner,
st.table_name segment_name,
st.partition_name partition_name ,
st.subpartition_name subpartition_name,
st.last_analyzed last_analyzed
from v$sql_plan sq,
dba_tab_statistics st
where sq.OBJECT_OWNER = st.owner
and sq.OBJECT_NAME = st.table_name
and sq.object_type = 'TABLE'
and sq.object_owner not in ('SYS','SYSTEM')
and nvl(st.stale_stats,'YES') = 'YES'
union all
select sq.sql_id,
sq.object_type type,
si.owner owner,
si.table_name segment_name,
si.partition_name partition_name ,
si.subpartition_name subpartition_name,
si.last_analyzed last_analyzed
from v$sql_plan sq,
dba_ind_statistics si
where sq.OBJECT_OWNER = si.owner
and sq.OBJECT_NAME = si.index_name
and sq.object_type = 'INDEX'
and sq.object_owner not in ('SYS','SYSTEM')
and nvl(si.stale_stats,'YES') = 'YES'
order by sql_id
/

viernes, 6 de marzo de 2009

Script para analizar tendendencia de crecimiento de tablespaces

A continuación les voy a pasar un script que permite monitorear como van llenandose los tablespaces y ademas obtiene una proyección de crecimiento a futuro. A mi me sirve para minimizar el riesgo de que se queden sin espacio los tablespaces (no suelo usar autoextend) y así poder realizar realocaciones o resizing en forma anticipada.


Rem
Rem Tendencia_Crecimiento_x_Tablespace
Rem
Rem NOMBRE
Rem Tendencia_Crecimiento_x_Tablespace.sql
Rem
Rem DESCRIPCION
Rem Reporte para mostrar como fue creciendo un tablespace por hora, dia,
Rem semana y mes. Tambien realiza proyecciones de crecimiento por semana -
Rem mes y muestra STATUS (Aplica para 10+)
Rem
Rem
Rem provedo 04/03/09 -- Creado
Rem
set line 150
col "%Used" format a10
col "%Proy_1s" format a10
col "%Proy_1m" format a10
col tsname format a20
select tsname,
round(tablespace_size*t2.block_size/
1024/1024,2) TSize,
round(tablespace_usedsize*t2.block_size/1024/1024,2) TUsed,
round((tablespace_size-tablespace_usedsize)*t2.block_size/1024/1024,2) TFree,
round(val1*t2.block_size/1024/1024,2) "Dif_1h",
round(val2*t2.block_size/1024/1024,2) "Dif_1d",
round(val3*t2.block_size/1024/1024,2) "Dif_1s",
round(val4*t2.block_size/1024/1024,2) "Dif_1m",
round((tablespace_usedsize/tablespace_size)*100)||'%' "%Used",
round(((tablespace_usedsize+val3)/tablespace_size)*100)||'%' "%Proy_1s",
round(((tablespace_usedsize+val4)/tablespace_size)*100)||'%' "%Proy_1m",
case when ((((tablespace_usedsize+val3)/tablespace_size)*100 < 80) and
(((tablespace_usedsize+val4)/tablespace_size)*100 < 80)) then 'NORMAL'
when ((((tablespace_usedsize+val3)/tablespace_size)*100 between 80 and 90)
or
(((tablespace_usedsize+val4)/tablespace_size)*100 between 80 and 90))
then 'WARNING'
else 'CRITICAL' end STATUS
from
(select distinct tsname,
rtime,
tablespace_size,
tablespace_usedsize,
tablespace_usedsize-first_value(tablespace_usedsize)
over (partition by tablespace_id order by rtime rows 1 preceding) val1,
tablespace_usedsize-first_value(tablespace_usedsize)
over (partition by tablespace_id order by rtime rows 24 preceding) val2,
tablespace_usedsize-first_value(tablespace_usedsize)
over (partition by tablespace_id order by rtime rows 168 preceding) val3,
tablespace_usedsize-first_value(tablespace_usedsize)
over (partition by tablespace_id order by rtime rows 720 preceding) val4
from (select t1.tablespace_size, t1.snap_id, t1.rtime,t1.tablespace_id,
t1.tablespace_usedsize-nvl(t3.space,0) tablespace_usedsize
from dba_hist_tbspc_space_usage t1,
dba_hist_tablespace_stat t2,
(select ts_name,sum(space) space
from recyclebin group by ts_name) t3
where t1.tablespace_id = t2.ts#
and t1.snap_id = t2.snap_id
and t2.tsname = t3.ts_name (+)) t1,
dba_hist_tablespace_stat t2
where t1.tablespace_id = t2.ts#
and t1.snap_id = t2.snap_id) t1,
dba_tablespaces t2
where t1.tsname = t2.tablespace_name
and rtime = (select max(rtime) from dba_hist_tbspc_space_usage)
and t2.contents = 'PERMANENT'
order by "Dif_1h" desc,"Dif_1d" desc,"Dif_1s" desc, "Dif_1m" desc


Un ejemplo de la salida resultado de correr el script es la siguiente:


TSNAME TSIZE TUSED TFREE Dif_1h Dif_1d Dif_1s Dif_1m %Used %Proy_1s %Proy_1m STATUS
-------------------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- --------
SYSAUX 580 551 29 .13 -3.31 -17.81 -17.81 95% 92% 92% CRITICAL
TS_TEL 61440 41097.13 20342.88 0 748.63 -2776 -2776 67% 62% 62% NORMAL
DESA_TS 7552 3278.56 4273.44 0 12 14.19 14.19 43% 44% 44% NORMAL
USERS 23205 720.44 22484.56 0 .06 .25 .25 3% 3% 3% NORMAL
SYSTEM 3620 1067.75 2552.25 0 0 2.06 2.06 29% 30% 30% NORMAL
TS_DATA 10240 10220.44 19.56 0 0 0 0 100% 100% 100% CRITICAL
SCH_DATA 900 899.69 .31 0 0 0 0 100% 100% 100% CRITICAL
EXAMPLE 100 68.19 31.81 0 0 0 0 68% 68% 68% NORMAL
ROP 1024 0 1024 0 0 0 0 0% 0% 0% NORMAL
TS_INDEX 4096 506.31 3589.69 0 0 0 0 12% 12% 12% NORMAL
SCH_INDEX 100 6.94 93.06 0 0 0 0 7% 7% 7% NORMAL


Ahora voy a describir las columnas del reporte:

TSNAME : Nombre del Tablespace
TSIZE : Espacio total del Tablespace en Mb (no toma en cuenta autoextend del
tablespace)
TUSED : Espacio utilizado (Mb)
TFREE : Espacio Libre (Mb)
Dif_1h : Diferencia entre el espacio alocado hace 1 hora y el espacio actual
(Mb).
Dif_1d : Diferencia entre el espacio alocado hace 1 dia y el espacio actual
(Mb).
Dif_1s : Diferencia entre el espacio alocado hace 1 semana y el espacio actual
(Mb).
Dif_1m : Diferencia entre el espacio alocado hace 1 mes y el espacio actual
(Mb).
%Used : Porcentaje de Uso actual del tablespace.
%Proy_1s : Porcentaje de Uso Proyectado a 1 semana adelante.
%Proy_1m : Porcentaje de Uso Proyectado a 1 mes adelante.
STATUS : Status de alocación: menor al 80% es normal, entre 80% y 90% es
warning y mayor al 90% es critical.

El script toma en cuenta que el AWR guarda 1 mes de historia y corre cada 1 hora.