<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-5727738329548735676</id><updated>2012-01-29T05:47:14.900-08:00</updated><category term='Mantenimiento'/><category term='Tuning'/><category term='11g R2'/><category term='Performance'/><category term='Diagnóstico'/><category term='Upgrade'/><category term='SQL'/><category term='Monitoreo'/><category term='Procesamiento'/><category term='CBO'/><category term='Scripts'/><category term='PL/SQL'/><category term='Administración'/><category term='Seguridad'/><category term='11g New Features'/><category term='DataWarehousing'/><category term='Conceptos'/><category term='Capacity Planning'/><category term='Diseño'/><category term='Misceláneo'/><category term='Configuración'/><title type='text'>OraMDQ</title><subtitle type='html'>Notas de interes de mi experiencia usando bases de datos Oracle</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://oramdq.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://oramdq.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Pablo A. Rovedo</name><uri>http://www.blogger.com/profile/02924687287216878757</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://3.bp.blogspot.com/_scsqOe7UOdc/SYDOV9ANLfI/AAAAAAAAAFQ/71M2XO1AZ1M/S220/DSC02067+(Small).JPG'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>76</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-5727738329548735676.post-3615985031880484647</id><published>2011-07-31T06:11:00.000-07:00</published><updated>2011-08-01T05:39:03.676-07:00</updated><title type='text'>Consideraciones importantes antes de cambiar tablas a modo nologging</title><content type='html'>&lt;div style="text-align: justify;"&gt;  Las tablas en una base Oracle por default registran en redo todos los cambios (transacciones) que se le fueron aplicando. Esto sirve para recuperar, ante una caida abrupta de la base de datos, aplicando un procedimiento denominado rolling forward,  las  transacciones confirmadas que no llegaron a impactarse en los datafiles. El grabado en redo es sincronico y muchas veces es causante de demoras generales en la base de datos, en especial en bases OLTP con alta concurrencia y alta tasa transaccionabilidad, uno de los eventos de espera mas comunes es el log file sync. &lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;Cuando en un reporte AWR o statspack vemos entre los TOp 5 events este tipo de eventos y buscamos en manuales, metalink o en foros, las dos recomendaciones principales son: a) bajar la transaccionabilidad, esto es minimizar los commits, y/o rollbacks, todo lo que sea posible sin afectar las reglas de negocio modeladas ó b) optimizar la escritura en redo, usando por ejemplo discos mas rápidos, recordemos que la escritura en redos, al contrario de la escritura en datafiles, es escritura secuencial, ideal es poner los redos sobre discos de estado solido, por la muy baja latencia y tambien verificar que no esten alojados sobre raid 5, siempre se recomienda raid 1 para estos archivos.&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;Otra opción interesante es pensar en generar la minima cantidad de redo sin modificar ninguno de los dos puntos tratados en el parrafo anterior. Cambiar a un tabla a nologging permite minimizar el redo generado para ciertas operaciones. Un mito muy común es poner todas las tablas en nologging y pensar que esto impide la registración de cambios en redo, nada mas lejos. La anulación de redo es imposible, ya que es un mecanismo fundamental del funcionamiento de la base de datos, lo que si se logra es reducir bastante la cantidad de redo, con la consecuencia que esto implica, que es ni mas ni menos que ante una falla de la instancia la operacion realizada en modo nologging no se puede recuperar, la unica opción es reprocesar. Las siguientes operaciones son las unicas disponibles para usar nologging:&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;ol&gt;&lt;li&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse; font-family: 'Times New Roman'; font-size: medium; "&gt;    &lt;/span&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse; font-family: 'Times New Roman'; font-size: medium; "&gt;create table...as select&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse; font-family: 'Times New Roman'; font-size: medium; "&gt;    create index&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse; font-family: 'Times New Roman'; font-size: medium; "&gt;    direct load con SQL*Loader&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse; font-family: 'Times New Roman'; font-size: medium; "&gt;    direct load INSERT (usando el hint APPEND)&lt;/span&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse; font-family: 'Times New Roman'; font-size: medium; "&gt; &lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse; font-family: 'Times New Roman'; font-size: medium; "&gt;    alter table...move partition&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse; font-family: 'Times New Roman'; font-size: medium; "&gt;    alter table...split partition&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse; font-family: 'Times New Roman'; font-size: medium; "&gt;    alter index...split partition&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse; font-family: 'Times New Roman'; font-size: medium; "&gt;    alter index...rebuild&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse; font-family: 'Times New Roman'; font-size: medium; "&gt;    alter index...rebuild partition&lt;/span&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;div&gt;&lt;span class="Apple-style-span"&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse; "&gt;Ahora, como siempre, comprobemos las operaciones que registran, y las que no, los cambios sobre redo:&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse; "&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse; "&gt;Voy a crear una tabla en modo logging (default) y luego voy a a ver el redo que consumió dicha operación.&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse; "&gt;&lt;/span&gt;&lt;/span&gt;&lt;pre&gt;&lt;span class="Apple-style-span"&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse; "&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse; "&gt;SQLPLUS&amp;gt; create table t as select * from dba_tables&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse; "&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse; font-size: large; font-family: 'Times New Roman'; "&gt;SQLPLUS&amp;gt;@myredosize.sql&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse; "&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse; font-size: large; font-family: 'Times New Roman'; "&gt;SQLPLUS&amp;gt; 1610k&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse; font-size: large; font-family: 'Times New Roman'; "&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse;"&gt;La cantidad de redo fue de 1610k, veamos que sucede si creamos la tabla en modo nologging:&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse; font-size: large; font-family: 'Times New Roman'; "&gt;&lt;pre&gt;&lt;/pre&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse; font-size: large; font-family: 'Times New Roman'; "&gt;SQLPLUS&amp;gt; create table t nologging as select * from dba_tables&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse; font-size: large; font-family: 'Times New Roman'; "&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse; font-size: large; font-family: 'Times New Roman'; "&gt;SQLPLUS&amp;gt;@myredosize.sql&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse; font-size: large; font-family: 'Times New Roman'; "&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse; font-size: large; font-family: 'Times New Roman'; "&gt;SQLPLUS&amp;gt; 100k&lt;span class="Apple-tab-span" style="white-space:pre"&gt; &lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse;"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse;"&gt;El redo usado no fue nulo pero si mucho menor, en el caso aproximadamente 16 veces inferior al de crear la tabla en modo logging.&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span class="Apple-style-span"&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse; "&gt;En el siguiente ejemplo vamos a comparar el redo insumido en una operacion de insert convencional con un insert en modo directo, ambos sobre  una tabla nologging:&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse; "&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse; "&gt;&lt;/span&gt;&lt;/span&gt;&lt;pre&gt;&lt;span class="Apple-style-span"&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse;"&gt;SQLPLUS&amp;gt; create table t nologging as select * from dba_tables where 1=0&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse; "&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse; "&gt;SQLPLUS&amp;gt; insert into t select * from dba_tables &lt;span class="Apple-tab-span" style="white-space:pre"&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse; "&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse; font-size: large; font-family: 'Times New Roman'; "&gt;SQLPLUS&amp;gt;@myredosize.sql&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse; font-size: large; font-family: 'Times New Roman'; "&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse; font-size: large; font-family: 'Times New Roman'; "&gt;SQLPLUS&amp;gt; 1497k&lt;span class="Apple-tab-span" style="white-space: pre; "&gt; &lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span class="Apple-style-span"&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse; "&gt;SQLPLUS&amp;gt; insert into t select /*+ APPEND */ * from dba_tables &lt;span class="Apple-tab-span" style="white-space: pre; "&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse; "&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse; font-size: large; font-family: 'Times New Roman'; "&gt;SQLPLUS&amp;gt;@myredosize.sql&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse; font-size: large; font-family: 'Times New Roman'; "&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse; font-size: large; font-family: 'Times New Roman'; "&gt;SQLPLUS&amp;gt; 34k&lt;span class="Apple-tab-span" style="white-space: pre; "&gt; &lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse; "&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse; "&gt;Lo que se puede observar es que en el primer caso, el consumo de redo fue similar al del create table del ejemplo anterior en modo logging. Esto muestra claramente que no todas las operaciones inhiben el redo, en el caso del insert convencional sobre una tabla en nologging no hay diferencia de hacerlo sobre una tabla logging. La diferencia se ve en el insert directo, que justamente es una de las pocas tipos de sentencias que aprovechan el nologging.&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse; "&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse; "&gt;Como siempre, es recomendable leer la documentación detenidamente para ver si tal funcionalidad o tal caracteristica es beneficiosa para lo que queremos hacer. Si por el contrario no entendemos bien cierto funcionamiento, tal vez, como es en el caso descripto en esta nota, tendemos a setear todas las tablas que podamos en nologging sin reducir como esperabamos el redo. Además hay que saber evaluar las consecuencias negativas que podría ocasionar tener tablas en nologging y perder la información al no estar sustentada con los redo's.&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse; "&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"&gt;&lt;span class="Apple-style-span" style="border-collapse: collapse; "&gt;&lt;span class="Apple-style-span"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt; &lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5727738329548735676-3615985031880484647?l=oramdq.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://oramdq.blogspot.com/feeds/3615985031880484647/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://oramdq.blogspot.com/2011/07/mitos-y-verdades-sobre-las-tablas-en.html#comment-form' title='1 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/3615985031880484647'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/3615985031880484647'/><link rel='alternate' type='text/html' href='http://oramdq.blogspot.com/2011/07/mitos-y-verdades-sobre-las-tablas-en.html' title='Consideraciones importantes antes de cambiar tablas a modo nologging'/><author><name>Pablo A. Rovedo</name><uri>http://www.blogger.com/profile/02924687287216878757</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://3.bp.blogspot.com/_scsqOe7UOdc/SYDOV9ANLfI/AAAAAAAAAFQ/71M2XO1AZ1M/S220/DSC02067+(Small).JPG'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5727738329548735676.post-8478880727797753849</id><published>2011-03-23T08:23:00.000-07:00</published><updated>2011-03-29T07:59:51.005-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='11g New Features'/><category scheme='http://www.blogger.com/atom/ns#' term='Procesamiento'/><category scheme='http://www.blogger.com/atom/ns#' term='Performance'/><title type='text'>Como cambiar el umbral de tolerancia de cambios para recolección estadística en 11g (STALE_TOLERANCE)</title><content type='html'>&lt;div style="text-align: justify;"&gt;En varios articulos escribí sobre las estadisticas y su importancia para el correcto funcionamiento del optimizador por costos (CBO). Mantener las estadisticas al dia es a veces una tarea bastante compleja y tediosa, en especial en entornos con gran volumen de datos y alta tasa de cambios. Asegurar que en cada ejecución de sentencias se cuente con estadisticas "frescas" es todo un desafio para los arquitectos y dba's. &lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;A partir de 10g se automatizó bastante dicha tarea, ya que uno de los procesos que corren durante la ventana de mantenimiento, es justamente la recolección estadistica. Para optimizar la recolección solo se actualizan las tablas cuya tasa de cambio sea mayor al 10%. Se puede consultar que tablas estan desactualizadas consultando la vista de catálogo DBA_TAB_STATISTICS, en donde hay un campo llamado STALE_STATS que puede tomar dos valores YES (la tabla necesita nuevas estadisticas) o NO (la tabla no necesita nuevas estadisticas). El umbral es fijo en 10g y no puede modificarse. Ya que la ventana de mantenimiento esta configurada para activarse durante la noche por default, si por ejemplo, un proceso de cambio masivo sobre una tabla genera cambios por mas del 10% no tendremos estadisticas frescas hasta el otro dia. En esos casos se recomienda recolectar estadisticas manualmente inmediatamente despues de la operatoria de cambio sobre las tablas involucradas. &lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;En 11g se puede cambiar el umbral a nivel de tabla, esquema o de la base completa, con lo cual se puede hacer tan sensible la toma de estadisticas como se requiera. En la práctica he usado dicho feature solo con granularidad de tabla en casos donde se detectaron cambios de planes de sentencias que referencian ciertas tablas con cambios menores al 10%.  A continuación voy a mostrar como usar el nuevo procedure SET_TABLE_PREFS del paquete DBMS_STATS para cambiar el umbral.&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;Repasando, en 11g se agregaron los siguiente procedimientos al paquete DBMS_STATS&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;SET_TABLE_PREFS&lt;/div&gt;&lt;div style="text-align: justify;"&gt;SET_SCHEMA_PREFS&lt;/div&gt;&lt;div style="text-align: justify;"&gt;SET_DATABASE_PREFS&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;Con los sp's listados arriba se puede realizar las siguientes 3 nuevas configuraciones:&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;STALE_PERCENT:  Para cambiar el umbral que determina cuando una tabla no tiene sus estadisticas al dia.&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;INCREMENTAL: Para optimizar la recolección sobre tablas particionadas (ver articulo xxx)&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;PUBLISH: Para testea un nuevo set de estadisticas antes de publicarlas&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;Voy a mostrar un ejemplo para cambiar el STALE_PERCENT de una tabla, consultando sobre el catalogo para que se vea como  se van registrando los cambios:&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;Primero voy a crear una tabla y luego tomo le tomo las estadisticas manualmente.&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;pre&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;div style="text-align: justify;"&gt;&lt;pre&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;create table t as select * from dba_objects&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;select count(1) from t&lt;/div&gt;&lt;div style="text-align: justify;"&gt; &lt;/div&gt;&lt;div style="text-align: justify;"&gt;begin&lt;/div&gt;&lt;div style="text-align: justify;"&gt;    dbms_stats.gather_table_stats(ownname = user; tabname = 'T');&lt;/div&gt;&lt;div style="text-align: justify;"&gt;end;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;select num_rows,stale_stats from user_tab_statistics where table_name = 'T'&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;NUM_ROWS    : 88538&lt;/div&gt;&lt;div style="text-align: justify;"&gt;STALE_STATS: NO&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;La columna STALE_STATS nos permite determinar si las estadisticas estan frescas o no. Una práctica común que he visto muchas veces, es mirar la columna LAST_ANALYZED de la vista USER_TABLES. Claramente este valor puede ser engañoso, ya que se tiende a inferir que cuanto mas vieja haya sido la ultima toma mas desactualizada estará la tabla, pero... si la tabla no tuvo cambios importantes desde la ultima recolección?, en ese caso el campo STALE_STATS estará en NO y el LAST_ANALYZED podría tener varios dias o incluso meses. Esto ultimo no implica en absoluto que las stats de la tabla estén desactualizadas. Como regla, siempre recomiendo mirar la columna STALE_STATS para determinar si una tabla tiene las estadisticas correctas, y solo ver el LAST_ANALYZED como un dato adicional.&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;Ahora voy a generar cambios de tipos diversos a la tabla, de forma tal de generar mas del 10% de cambios, recordar que es el umbral de tolerancia default (STALE_TOLERANCE)&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;pre&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;div style="text-align: justify;"&gt;update t set object_id = rownum&lt;/div&gt;&lt;div style="text-align: justify;"&gt;where rownum &amp;lt;= 3000&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;delete t &lt;/div&gt;&lt;div style="text-align: justify;"&gt;where rownum &amp;lt;= 3000&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;insert into t&lt;/div&gt;&lt;div style="text-align: justify;"&gt;select * from dba_objects&lt;/div&gt;&lt;div style="text-align: justify;"&gt;where rownum &amp;lt;= 3000&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;Voy a usar la vista USER_TAB_MODIFICATIONS que muestra la cantidad de DML´s por cada tabla desde la ultima toma de estadisticas. Pueden usar la info de dicha tabla, para conocer la tasa de cambios y el tipo de operaciones, lo cual resulta de mucha utilidad para conocer mas acerca de la operatoria en la base de datos. &lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;pre&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;div style="text-align: justify; "&gt;SQL&amp;gt; select inserts,updates,deletes from user_tab_modifications where table_name = 'T';&lt;/div&gt;&lt;div style="text-align: justify; "&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify; "&gt;no rows selected&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;No hay registros para la tabla, que raro, no?, si recien habia realizado cambios importantes. En realidad no es raro, el tema es que los cambios primero se almacenan en memoria y son "flusheados" a disco cada 30'. Para forzar el flush hacemos:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;pre&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;div style="text-align: justify;"&gt;begin&lt;/div&gt;&lt;div style="text-align: justify;"&gt;    dbms_stats.flush_database_monitoring_info;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;end;&lt;/div&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;div style="text-align: justify;"&gt;SQL&amp;gt; select inserts,updates,deletes from user_tab_modifications where table_name = 'T';&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;   INSERTS    UPDATES    DELETES&lt;/div&gt;&lt;div style="text-align: justify;"&gt;---------- ---------- ----------&lt;/div&gt;&lt;div style="text-align: justify;"&gt;      3000       3000       3000&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Ahora si aparecen los cambios, tal cual se esperaba. Chequeamos si las estadisticas se marcan como "viejas":&lt;/div&gt;&lt;div&gt;&lt;pre&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div&gt;&lt;div style="text-align: justify; "&gt;select num_rows,stale_stats from user_tab_statistics where table_name = 'T'&lt;/div&gt;&lt;div style="text-align: justify; "&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify; "&gt;NUM_ROWS    : 88538&lt;/div&gt;&lt;div style="text-align: justify; "&gt;STALE_STATS: YES&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Justamente, una vez impactados los cambios en el catalogo tambien se actualizó la columna STALE_STATS y pasó de NO a YES.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Con la intro que realicé mas arriba, ahora puedo mostrarles como cambiar el umbral para la tabla T, para que ahora en lugar de tomar el umbral global default, utilice un umbral mayor: &lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;div style="text-align: justify; "&gt;&lt;pre&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;div style="text-align: justify;"&gt;begin&lt;/div&gt;&lt;div style="text-align: justify;"&gt;    dbms_stats.set_table_prefs(user,'T','STALE_PERCENT','15'); &lt;/div&gt;&lt;div style="text-align: justify;"&gt;end;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;Vuelvo a realizar los inserts, updates y deletes anteriores, realizo flush de cache para actualizar el catálogo y reviso si las estadísticas de la tabla están marcadas como STALE:&lt;/div&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;pre&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;div style="text-align: justify; "&gt;update t set object_id = rownum&lt;/div&gt;&lt;div style="text-align: justify; "&gt;where rownum &amp;lt;= 3000&lt;/div&gt;&lt;div style="text-align: justify; "&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify; "&gt;delete t &lt;/div&gt;&lt;div style="text-align: justify; "&gt;where rownum &amp;lt;= 3000&lt;/div&gt;&lt;div style="text-align: justify; "&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify; "&gt;insert into t&lt;/div&gt;&lt;div style="text-align: justify; "&gt;select * from dba_objects&lt;/div&gt;&lt;div style="text-align: justify; "&gt;where rownum &amp;lt;= 3000&lt;/div&gt;&lt;div style="text-align: justify; "&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify; "&gt;&lt;div style="text-align: justify; "&gt;begin&lt;/div&gt;&lt;div style="text-align: justify; "&gt;    dbms_stats.flush_database_monitoring_info;&lt;/div&gt;&lt;div style="text-align: justify; "&gt;end;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;div style="text-align: justify; "&gt;select num_rows,stale_stats from user_tab_statistics where table_name = 'T'&lt;/div&gt;&lt;div style="text-align: justify; "&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify; "&gt;NUM_ROWS    : 88538&lt;/div&gt;&lt;div style="text-align: justify; "&gt;STALE_STATS: NO&lt;/div&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;Como es observa, ahora las estadísticas no estan desactualizadas para Oracle y por lo tanto no se recolectarán las estadisticas para la tabla en la próxima ventana de mantenimiento. Este nuevo feature permite mayor granularidad para determinar cuando una tabla necesita estadisticas y cuando no se requieren, con lo cual se minimizan los tiempos de recolección, adecuando con mayor precisión dicho proceso a las necesidades particulares de cada tabla, esquema o base de datos. &lt;/div&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;span class="Apple-style-span"&gt;&lt;p class="style9" style="margin-left: 40px; "&gt;&lt;/p&gt;&lt;p class="style9" style="margin-left: 40px; "&gt;&lt;br /&gt;&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5727738329548735676-8478880727797753849?l=oramdq.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://oramdq.blogspot.com/feeds/8478880727797753849/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://oramdq.blogspot.com/2011/03/como-cambiar-el-umbral-de-tolerancia-de.html#comment-form' title='3 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/8478880727797753849'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/8478880727797753849'/><link rel='alternate' type='text/html' href='http://oramdq.blogspot.com/2011/03/como-cambiar-el-umbral-de-tolerancia-de.html' title='Como cambiar el umbral de tolerancia de cambios para recolección estadística en 11g (STALE_TOLERANCE)'/><author><name>Pablo A. Rovedo</name><uri>http://www.blogger.com/profile/02924687287216878757</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://3.bp.blogspot.com/_scsqOe7UOdc/SYDOV9ANLfI/AAAAAAAAAFQ/71M2XO1AZ1M/S220/DSC02067+(Small).JPG'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5727738329548735676.post-8631118570772981175</id><published>2011-02-18T04:04:00.000-08:00</published><updated>2011-02-21T14:48:30.941-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Misceláneo'/><title type='text'>Factorial de n usando una sentencia SQL simple.</title><content type='html'>&lt;div style="text-align: justify;"&gt;Quizás alguna vez se han preguntado porque no existe una función de agregación PROD, que obtenga el producto de una columna numerica o expresión de una tabla, me parece que no,  verdad?. Muy probablemente a la mayoria, nunca se le ha generado esa inquietud, porque de hecho, no tiene, a priori, demasiada utilidad en los sistemas reales. De todas formas,  la idea de la nota es mostrarles que tan simple y elegante puede ser una solución usando solo una simple sentencia SELECT, independientemente si le encuentran alguna utilidad o no.&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;div style="text-align: justify;"&gt;Como una breve introducción, voy a repasar las funciones de agregación, que son variadas. Las mas conocidas y usadas mas frecuentemente son:&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;SUM:  Retorna la suma de una columna o expresión&lt;br /&gt;AVG:  Retorna el promedio de una columa o expresión&lt;br /&gt;MIN:  Retorna el valor mínimo&lt;br /&gt;MAX: Retorna el valor máximo&lt;br /&gt;&lt;br /&gt;&lt;div style="text-align: justify;"&gt;Otras funciones, son las estadisticas, que suelo usar cuando necesito analizar tendencias y patrones, y son:&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;STDDEV      :  Retorna la desviación standard de una columna o expresión&lt;br /&gt;VARIANCE :  Retorna la varianza de una columna o expresión&lt;br /&gt;MEDIAN     :  Retorna la mediana de una columna o expresión&lt;br /&gt;&lt;br /&gt;existen muchas mas funciones de agregación para usar, ver detalle en el manual oficial para la versión de BD que necesiten, por ejemplo para 11g R2: &lt;a style="color: rgb(255, 0, 0); font-weight: bold;" href="http://download.oracle.com/docs/cd/E11882_01/server.112/e17118/functions003.htm#i89203" type="text/css" rel="stylesheet"&gt; SQL Reference 11.2 R2 (Funciones de Agregación)&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;div style="text-align: justify;"&gt;En esta oportunidad, voy a mostrar como simular la función PROD usando algunas reglas matematicas simples, y por ultimo, usar dicha función para obtener el factorial de un número dado:&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;Para comenzar, voy a definir las dos funciones que voy a utilizar:&lt;br /&gt;&lt;br /&gt;ln(n)    =   Retorna el logarimo natural de n&lt;br /&gt;&lt;br /&gt;y su función inversa:&lt;br /&gt;&lt;br /&gt;exp(n) =   Retorna &lt;span style="font-size:130%;"&gt;e&lt;/span&gt; elevado a la n&lt;br /&gt;&lt;br /&gt;Ahora, partiendo de la siguiente propiedad matematica de los logaritmos:&lt;br /&gt;&lt;br /&gt;ln(a) + Ln(b) = ln(a * b)&lt;br /&gt;&lt;br /&gt;Elevando e con cada lado de la ecuación anterior:&lt;br /&gt;&lt;br /&gt;exp(ln(a)+ln(b)) = exp(ln(a*b))&lt;br /&gt;&lt;br /&gt;&lt;div style="text-align: justify;"&gt;Dado que exp y ln son funciones inversas, entonces se anulan y nos queda a multiplicado por b:&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;exp(ln(a*b))  = a*b&lt;br /&gt;&lt;br /&gt;En este punto, ya tenemos definida y explicada la regla. Ahora voy a mostrar un ejemplo de uso, para obtener el factorial:&lt;br /&gt;&lt;br /&gt;La sentencia SQL para obtener el factorial de N tiene la siguiente forma:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;select exp(sum(ln(rownum))) from dual&lt;br /&gt;connect by rownum &lt;= N ; &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Un ejemplo usando como consola sqlplus:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA10G&gt; variable n number&lt;br /&gt;rop@DESA10G&gt; exec :n := 3;&lt;br /&gt;&lt;br /&gt;Procedimiento PL/SQL terminado correctamente.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;rop@DESA10G&gt; select exp(sum(ln(rownum))) from dual&lt;br /&gt;2            connect by rownum &lt;= :n ; &lt;br /&gt;&lt;br /&gt;EXP(SUM(LN(ROWNUM)))&lt;br /&gt;--------------------                   &lt;br /&gt;&lt;span style="font-weight:bold;"&gt;6&lt;/span&gt; &lt;br /&gt;&lt;br /&gt;rop@DESA10G&gt; exec :n := 6;&lt;br /&gt;&lt;br /&gt;Procedimiento PL/SQL terminado correctamente.&lt;br /&gt;&lt;br /&gt;rop@DESA10G&gt; select exp(sum(ln(rownum)))&lt;br /&gt;             from dual&lt;br /&gt;2            connect by rownum &lt;= :n ; &lt;br /&gt;&lt;br /&gt;EXP(SUM(LN(ROWNUM)))&lt;br /&gt;--------------------                 &lt;br /&gt;&lt;span style="font-weight:bold;"&gt;720 &lt;/span&gt; &lt;/pre&gt;&lt;br /&gt;Como se ve, se puede obtener el factorial de cualquier número con una simple sentencia. También se podria usar para multiplicar las columnas de una tabla, multiplicar expresiones, etc.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5727738329548735676-8631118570772981175?l=oramdq.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://oramdq.blogspot.com/feeds/8631118570772981175/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://oramdq.blogspot.com/2011/02/factorial-de-n-usando-una-sentencia-sql.html#comment-form' title='1 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/8631118570772981175'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/8631118570772981175'/><link rel='alternate' type='text/html' href='http://oramdq.blogspot.com/2011/02/factorial-de-n-usando-una-sentencia-sql.html' title='Factorial de n usando una sentencia SQL simple.'/><author><name>Pablo A. Rovedo</name><uri>http://www.blogger.com/profile/02924687287216878757</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://3.bp.blogspot.com/_scsqOe7UOdc/SYDOV9ANLfI/AAAAAAAAAFQ/71M2XO1AZ1M/S220/DSC02067+(Small).JPG'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5727738329548735676.post-6335200980990862585</id><published>2011-01-31T04:38:00.000-08:00</published><updated>2011-02-02T03:44:34.328-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='11g New Features'/><category scheme='http://www.blogger.com/atom/ns#' term='Procesamiento'/><category scheme='http://www.blogger.com/atom/ns#' term='Performance'/><title type='text'>Row Prefetching en Oracle</title><content type='html'>&lt;div style="text-align: justify;"&gt; Cada vez que una aplicación necesita obtener datos desde la base de datos, se lo solicita al driver y este ejecuta una cierta sentencia que retorna el resultado fila por fila, o mejor aún, retorna un conjunto de filas que son almacenadas del lado del cliente (caching de aplicación) y procesadas posteriormente.&lt;br /&gt;&lt;br /&gt;El mecánismo de retornar un conjunto de filas a vez se denomina "row prefetching" y sirve principalmente para minimizar las idas y vueltas a la base (round trips); los datos serán almacenados en la memoria y consumidos desde allí por la aplicación con la consiguiente mejora de rendimiento ya que se minimiza la comunicación con la base de datos. En esta nota tambien voy a mostrar ejemplos usando PL/SQL, Java y C#.&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: justify;"&gt;&lt;br /&gt;Voy a realizar una comparativa usando bloques PL/SQL para implementar cursores para procesar los datos de una tabla T con 100,000 registros y creada de la siguiente manera:&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;create table t (id int, pad varchar2(200));&lt;br /&gt;&lt;br /&gt;insert into t&lt;br /&gt;select rownum,dbms_random.string('a',200)&lt;br /&gt;from dual&lt;br /&gt;connect by rownum &lt;= 100000; &lt;/pre&gt;&lt;br /&gt;&lt;div style="text-align: justify;"&gt;Con la tabla creada, ahora voy a ejecutar el bloque que obtiene los datos a procesar con un cursor explicito, y voy a activar el trace para ver la cantidad de fetches que se requieren:&lt;br /&gt;&lt;/div&gt;&lt;pre&gt;&lt;br /&gt;declare&lt;br /&gt;cursor cur1 is select * from t;&lt;br /&gt;l_rec t%ROWTYPE;&lt;br /&gt;begin&lt;br /&gt;open cur1;&lt;br /&gt;&lt;br /&gt;loop&lt;br /&gt; fetch cur1 into l_rec;&lt;br /&gt; exit when cur1%notfound;&lt;br /&gt; null;&lt;br /&gt;end loop;&lt;br /&gt;close cur1;&lt;br /&gt;end;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;SELECT *&lt;br /&gt;FROM&lt;br /&gt;T&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;call     count       cpu    elapsed       disk      query    current        rows&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;Parse        1      0.00       0.00          0          1          0           0&lt;br /&gt;Execute      1      0.00       0.00          0          0          0           0&lt;br /&gt;Fetch   &lt;span style="font-weight: bold;"&gt;100001&lt;/span&gt;      0.99       0.76          0     100016          0      100000&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;total   100003      0.99       0.76          0     100017          0      100000&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;div style="text-align: justify;"&gt;Se observa desde la salida del trace (previamente procesada con tkprof) que la cantidad de fetches es igual a la cantidad de registros,es decir, se realizó un fetch por cada fila. Probemos realizar la misma operatoria pero ahora usando cursores implicitos:&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;begin&lt;br /&gt;for i in (select * from t)&lt;br /&gt;loop&lt;br /&gt;null;&lt;br /&gt;end loop;&lt;br /&gt;end;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;SELECT *&lt;br /&gt;FROM&lt;br /&gt;T&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;call     count       cpu    elapsed       disk      query    current        rows&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;Parse        1      0.00       0.00          0          0          0           0&lt;br /&gt;Execute      1      0.00       0.00          0          0          0           0&lt;br /&gt;Fetch     &lt;span style="font-weight: bold;"&gt;1001&lt;/span&gt;      0.23       0.22          0       3899          0      100000&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;total     1003      0.23       0.22          0       3899          0      100000&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;div style="text-align: justify;"&gt;Con cursores implicitos se necesitaron 1001, por lo cual haciendo una simple cuenta podemos afirmar que el prefetch fue de 100 filas, valor fijo y siempre y cuando el parametro plsql_optimize_level sea 2 (default a partir de 10g R2). Además notar que las lecturas lógicas y el tiempo de procesamiento total es menor cuando se usa prefetch.&lt;br /&gt;&lt;br /&gt;Esta podría ser una de las tantas justificaciones para tentarlos a usar cursores implicitos, no?. En general yo suelo usar cursores implicitos (CI), ya que escribo menos, y además he probado que son mas eficientes que los cursores explicitos (CE). Claro, que en casos particulares no nos queda otra opción que usar CE cuando queremos tener mas control de todos las etapas (declare,open,fetch y close). Sin embargo con la introducción de BULK COLLECT podemos mejorar la performance con CE de forma tal de paralelizar. Tambien con bulk collect podemos definir facilmente el tamaño del fetch. Ahi va un ejemplo:&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;declare&lt;br /&gt;cursor cur1 is select * from t;&lt;br /&gt;type t_type is table of t%ROWTYPE;&lt;br /&gt;l_t t_type;&lt;br /&gt;begin&lt;br /&gt;open cur1;&lt;br /&gt;loop&lt;br /&gt; fetch cur1 BULK COLLECT into l_t LIMIT 100;&lt;br /&gt; exit when cur1%notfound;&lt;br /&gt; for i in l_t.first..l_t.last&lt;br /&gt; loop&lt;br /&gt;    null;&lt;br /&gt; end loop;&lt;br /&gt;end loop;&lt;br /&gt;close cur1;&lt;br /&gt;end;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;call     count       cpu    elapsed       disk      query    current        rows&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;Parse        1      0.00       0.00          0          0          0           0&lt;br /&gt;Execute      1      0.00       0.00          0          0          0           0&lt;br /&gt;Fetch     &lt;span style="font-weight: bold;"&gt;1001&lt;/span&gt;      0.25       0.24          0       3899          0      100000&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;total     1003      0.25       0.24          0       3899          0      100000&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;div style="text-align: justify;"&gt;En el ejemplo anterior definí un tamaño de prefetch de 100, tal cual se puede verificar en la salida del trace. Definamos ahora  un  tambaño de fetch mayor (1000):&lt;br /&gt;&lt;/div&gt;&lt;pre&gt;&lt;br /&gt;declare&lt;br /&gt;cursor cur1 is select * from t;&lt;br /&gt;type t_type is table of t%ROWTYPE;&lt;br /&gt;l_t t_type;&lt;br /&gt;begin&lt;br /&gt;open cur1;&lt;br /&gt;loop&lt;br /&gt; fetch cur1 BULK COLLECT into l_t LIMIT 1000;&lt;br /&gt; exit when cur1%notfound;&lt;br /&gt; for i in l_t.first..l_t.last&lt;br /&gt; loop&lt;br /&gt;    null;&lt;br /&gt; end loop;&lt;br /&gt;end loop;&lt;br /&gt;close cur1;&lt;br /&gt;end;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;call     count       cpu    elapsed       disk      query    current        rows&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;Parse        1      0.00       0.00          0          0          0           0&lt;br /&gt;Execute      1      0.00       0.00          0          0          0           0&lt;br /&gt;Fetch      &lt;span style="font-weight: bold;"&gt;101&lt;/span&gt;      0.24       0.24          0       3052          0      100000&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;total      103      0.24       0.24          0       3052          0      100000&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;div style="text-align: justify;"&gt;Hicieron falta 101 llamadas a la base, cada una retornó 1000 filas al cliente. Dichas filas quedan almacenadas en la memoria del cliente para su posterior utilización.&lt;br /&gt;&lt;br /&gt;Por ultimo, voy a mostrar como se define el tamaño del fetch en java y C#:&lt;br /&gt;&lt;br /&gt;Porción de Código java para usar prefetch:&lt;br /&gt;&lt;/div&gt;&lt;pre&gt;&lt;br /&gt;try&lt;br /&gt;{&lt;br /&gt;sql = "select id, pad from t";&lt;br /&gt;statement = connection.prepareStatement(sql);&lt;br /&gt;statement.setFetchSize(100);&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;      resultset = statement.executeQuery();&lt;/span&gt;&lt;br /&gt;while (resultset.next())&lt;br /&gt;{&lt;br /&gt;id = resultset.getLong("id");&lt;br /&gt;pad = resultset.getString("pad");&lt;br /&gt;// Implementación de la lógica en el cuerpo del bucle&lt;br /&gt;}&lt;br /&gt;resultset.close();&lt;br /&gt;statement.close();&lt;br /&gt;}&lt;br /&gt;catch (SQLException e)&lt;br /&gt;{&lt;br /&gt;throw new Exception("Error : " + e.getMessage());&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;div style="text-align: justify;"&gt;Porción de Código C# (.NET) para usar prefetch:&lt;br /&gt;&lt;/div&gt;&lt;pre&gt;&lt;br /&gt;sql = "select id, pad from t";&lt;br /&gt;command = new OracleCommand(sql, connection);&lt;br /&gt;command.AddToStatementCache = false;&lt;br /&gt;reader = command.ExecuteReader();&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;   reader.FetchSize = command.RowSize * 100;&lt;/span&gt;&lt;br /&gt;while (reader.Read())&lt;br /&gt;{&lt;br /&gt;  id = reader.GetDecimal(0);&lt;br /&gt;  pad = reader.GetString(1);&lt;br /&gt;  // Implementación de la lógica en el cuerpo del buclejavascript:void(0)&lt;br /&gt;}&lt;br /&gt;reader.Close();&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Para ver mas sobre este tema y sobre otros temas de performance, recomiendo la lectura del excelente libro de Christian Antognini:&lt;a style="color: rgb(255, 0, 0); font-weight: bold;" href="http://www.amazon.com/Troubleshooting-Oracle-Performance-Christian-Antognini/dp/1590599179" type="text/css" rel="stylesheet"&gt; Troubleshooting Oracle Performance (by Christian-Antognini)&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5727738329548735676-6335200980990862585?l=oramdq.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://oramdq.blogspot.com/feeds/6335200980990862585/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://oramdq.blogspot.com/2011/01/row-prefetching-en-oracle.html#comment-form' title='0 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/6335200980990862585'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/6335200980990862585'/><link rel='alternate' type='text/html' href='http://oramdq.blogspot.com/2011/01/row-prefetching-en-oracle.html' title='Row Prefetching en Oracle'/><author><name>Pablo A. Rovedo</name><uri>http://www.blogger.com/profile/02924687287216878757</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://3.bp.blogspot.com/_scsqOe7UOdc/SYDOV9ANLfI/AAAAAAAAAFQ/71M2XO1AZ1M/S220/DSC02067+(Small).JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5727738329548735676.post-7723414334468468330</id><published>2011-01-27T05:43:00.000-08:00</published><updated>2011-01-31T04:07:05.732-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='11g New Features'/><category scheme='http://www.blogger.com/atom/ns#' term='Procesamiento'/><category scheme='http://www.blogger.com/atom/ns#' term='Diseño'/><category scheme='http://www.blogger.com/atom/ns#' term='Performance'/><title type='text'>Nueva funcionalidad para el SELECT FOR UPDATE (SKIP LOCKED)</title><content type='html'>&lt;div style="text-align: justify;"&gt;En muchas ocasiones tuve la oportunidad de revisar código pl/sql en donde se programa, entre otras cosas, la "marcación" de registros por medio de un flag. Estas marcas, en general se implementan modificando una columna (ej: estado) donde se denota en que etapa del procesamiento se encuentra la sesión y así evitar solapamientos con otras sesiones paralelas que esten haciendo lo mismo.&lt;br /&gt;&lt;br /&gt;La forma mas común que yo he visto para realizar la operatoria descripta es usando "SELECT ... FOR UPDATE NOWAIT" del registro de la tabla maestra para asegurar que las demás sesiones no puedan procesar dicho registro. Una vez que el registro se procesó, las otras sesiones podrán tomar el siguiente registro disponible para procesar. Si no se requiere un orden para procesar, este enfoque atenta contra el paralelismo real. Esto se da porque mientras una sesión este procesando un registro las otras deberan esperar a que se commitee para poder procesar el siguiente registro. Esto sucede porque Oracle no lockea los selects, entonces aunque el proceso este procesando un registro dado, no se puede "saltear" y tomar el siguiente, sino que se devuelve un error de que el recurso esta siendo usado (ORA-00054 recurso ocupado y obtenido con NOWAIT). &lt;br /&gt;&lt;br /&gt;A partir de 11g se documentó una opción (por lo que pude probar existe desde 9i pero no estaba documentada) muy interesante para lidiar con este tipo de procesamiento, que en realidad es una extensión de la sintaxis del for update para permitir, justamente, para poder procesar con mucho mayor grado de paralelismo y asi posibilitar que cada sesion "saltee" el registro en procesamiento y tome el próximo disponible para procesar.  Esto mejora sensiblemente los tiempos de procesamiento general, ya que se podrán levantar n sesiones en paralelo minimizando la interdependencia entre ellas.&lt;br /&gt;&lt;br /&gt;Abajo les muestro un ejemplo:&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Creo una tabla T y le inserto 100 registros:  &lt;br /&gt;&lt;pre&gt;&lt;br /&gt;create table t (id int,&lt;br /&gt;                estado char(1),&lt;br /&gt;                fecha date,&lt;br /&gt;                importe number(8,2))&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;insert into t&lt;br /&gt;select rownum,&lt;br /&gt;      'C',&lt;br /&gt;      sysdate+dbms_random.value(-50,50),&lt;br /&gt;      dbms_random.value(1,1000000)&lt;br /&gt;from dual&lt;br /&gt;connect by rownum &lt;= 100  &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Cambio el estado de 10 filas, elegidas al aleatoriamente. Dichas filas quedarán en estado 'P', suponiendo que el estado 'P' es disponibles para procesar.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;create view t_v as &lt;br /&gt;select id&lt;br /&gt;from t &lt;br /&gt;order by dbms_random.value  &lt;br /&gt;&lt;br /&gt;update t&lt;br /&gt;set estado = 'P'&lt;br /&gt;where id in (select id from t_v) &lt;br /&gt;and rownum &lt;= 10  &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;select * from t where estado = 'P';         &lt;br /&gt;&lt;br /&gt;       ID E FECHA        IMPORTE&lt;br /&gt;---------- - --------- ----------&lt;br /&gt;        1 P 20-DIC-10  888292.36&lt;br /&gt;       27 P 09-MAR-11  845864.47&lt;br /&gt;       39 P 19-ENE-11  583901.49&lt;br /&gt;       52 P 23-FEB-11  157817.12&lt;br /&gt;       62 P 05-ENE-11   680744.2&lt;br /&gt;       63 P 19-ENE-11  679375.69&lt;br /&gt;       73 P 20-ENE-11   750069.3&lt;br /&gt;       87 P 26-FEB-11  783555.02&lt;br /&gt;       96 P 13-DIC-10  973668.87&lt;br /&gt;      100 P 28-FEB-11  756671.07&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;En una consola ejecutamos el siguiente bloque pl, para tomar el siguiente registro a procesar (Sesion 1)&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;declare&lt;br /&gt;   cursor l_cur is&lt;br /&gt;   select  *&lt;br /&gt;   from  t&lt;br /&gt;   where estado = 'P'&lt;br /&gt;   for update nowait skip locked;&lt;br /&gt;   l_rec l_cur%rowtype;&lt;br /&gt;begin&lt;br /&gt;      open l_cur;&lt;br /&gt;      fetch l_cur into l_rec;   &lt;br /&gt;      -- &lt;aca va el código que implementa la reglas de negocio&gt;&lt;br /&gt;      dbms_output.put_line (l_rec.id);&lt;br /&gt;end;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Resultado: 1&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;En otra sesion (Sesion 2) ejecutamos el mismo bloque pl:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;declare&lt;br /&gt;   cursor l_cur is&lt;br /&gt;   select  *&lt;br /&gt;   from  t&lt;br /&gt;   where estado = 'P'&lt;br /&gt;   for update nowait skip locked;&lt;br /&gt;   l_rec l_cur%rowtype;&lt;br /&gt;begin&lt;br /&gt;      open l_cur;&lt;br /&gt;      fetch l_cur into l_rec;   &lt;br /&gt;      -- &lt;aca va el código que implementa la reglas de negocio&gt;&lt;br /&gt;      dbms_output.put_line (l_rec.id);&lt;br /&gt;end;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Resultado: 27&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt; &lt;br /&gt;&lt;br /&gt;La sesión 1 tomó el registro con id=1 y la sesión 2 tomó el registro con id=27, que son el primero y segundo respectivamente en el listado de mas arriba. Claramente no se commiteo nada y sin embargo la sesion 2 pudo tomar un nuevo registro para procesar mientras la sesión 1 estaba procesando. Con el select for update convencional la sesión 2 hubiese fallado y por código se deberia volver a intentar hasta que la sesión 1 libere el registro (commit/rollback) con id=1 y asi permitir pasar al siguiente.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5727738329548735676-7723414334468468330?l=oramdq.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://oramdq.blogspot.com/feeds/7723414334468468330/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://oramdq.blogspot.com/2011/01/nueva-funcionalidad-para-el-select-for.html#comment-form' title='0 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/7723414334468468330'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/7723414334468468330'/><link rel='alternate' type='text/html' href='http://oramdq.blogspot.com/2011/01/nueva-funcionalidad-para-el-select-for.html' title='Nueva funcionalidad para el SELECT FOR UPDATE (SKIP LOCKED)'/><author><name>Pablo A. Rovedo</name><uri>http://www.blogger.com/profile/02924687287216878757</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://3.bp.blogspot.com/_scsqOe7UOdc/SYDOV9ANLfI/AAAAAAAAAFQ/71M2XO1AZ1M/S220/DSC02067+(Small).JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5727738329548735676.post-2892595303432540044</id><published>2011-01-17T04:15:00.000-08:00</published><updated>2011-01-21T04:06:35.882-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Misceláneo'/><category scheme='http://www.blogger.com/atom/ns#' term='SQL'/><title type='text'>Un ejemplo de como realizar calculos con fechas con sql</title><content type='html'>Hace unos dias me llegó un mail de esos tipicos mails cadena que supuestamente traen suerte si se lo envias a 20 personas mas. Claramente estos mails no tienen sustento real en general y actuan por ingenieria social como mecanismo de generación de tráfico basura. Rara vez alcanzo a leer mas de 2 lineas antes de borrarlos, pero en este caso me llamó la atención y despertó mi curiosidad, ya que probar su veracidad con una consulta sql seria muy facil. Claramente esto no tiene ninguna utilidad mas que mostrarles como resolver cuestiones y relaciones de fechas usando, en este caso Oracle, como una&lt;br /&gt;calculadora extremadamente costosa.&lt;br /&gt;&lt;br /&gt;El mail decia algo asi: "Este año el mes de julio tiene 5 viernes, 5 sabados y 5 domingos, esto se da cada 823 años, esto se denomina saco de dinero, envia esto a 20 amigos... bla bla bla". Cuando lo leí, me dí cuenta que no podia ser real ya que tener esa periodicidad tan exacta y extensa, tratandose de fechas, no era posible. Para probarlo prolijamente, realicé una consulta de forma tal de generar fechas automaticamente, comenzando desde una fecha bien lejana (500000 años hacia atrás) y sumando cada vez que el mes fuera Julio (07) los dias viernes, sabado y domingo (6,7,1). Si la suma es 15 entonces en ese año se da que lo que reza el mail en cuestión. A continuación les muestro la consulta y el resultado: &lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;select to_char(dt,'YYYY') dt,count(1)&lt;br /&gt;from (select sysdate-500000+rownum dt&lt;br /&gt;     from dual&lt;br /&gt;     connect by rownum &lt;= 500000)&lt;br /&gt;where to_char(dt,'MM') = '07'  &lt;br /&gt; and to_char(dt,'d') in (6,7,1)&lt;br /&gt;group by to_char(dt,'YYYY')&lt;br /&gt;having count(1) &gt; 14&lt;br /&gt;order by to_number(dt) desc&lt;br /&gt;&lt;br /&gt; 1    2005    15&lt;br /&gt; 2    1994    15&lt;br /&gt; 3    1988    15&lt;br /&gt; 4    1983    15&lt;br /&gt; 5    1977    15&lt;br /&gt; 6    1966    15&lt;br /&gt; 7    1960    15&lt;br /&gt; 8    1955    15&lt;br /&gt; 9    1949    15&lt;br /&gt;10    1938    15&lt;br /&gt;11    1932    15&lt;br /&gt;12    1927    15&lt;br /&gt;13    1921    15&lt;br /&gt;14    1910    15&lt;br /&gt;15    1904    15&lt;br /&gt;16    1898    15&lt;br /&gt;17    1892    15&lt;br /&gt;18    1887    15&lt;br /&gt;19    1881    15&lt;br /&gt;20    1870    15&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Como se ve, en el 2005 se dió por ultima vez la relación citada, pasaron solo 6 años para que se repita y no 823!!!.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5727738329548735676-2892595303432540044?l=oramdq.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://oramdq.blogspot.com/feeds/2892595303432540044/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://oramdq.blogspot.com/2011/01/un-ejemplo-de-como-realizar-calculos.html#comment-form' title='0 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/2892595303432540044'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/2892595303432540044'/><link rel='alternate' type='text/html' href='http://oramdq.blogspot.com/2011/01/un-ejemplo-de-como-realizar-calculos.html' title='Un ejemplo de como realizar calculos con fechas con sql'/><author><name>Pablo A. Rovedo</name><uri>http://www.blogger.com/profile/02924687287216878757</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://3.bp.blogspot.com/_scsqOe7UOdc/SYDOV9ANLfI/AAAAAAAAAFQ/71M2XO1AZ1M/S220/DSC02067+(Small).JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5727738329548735676.post-8680828294825532982</id><published>2011-01-14T07:32:00.000-08:00</published><updated>2011-01-19T03:35:49.902-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Diagnóstico'/><category scheme='http://www.blogger.com/atom/ns#' term='Scripts'/><title type='text'>Análisis de consumo de espacio REDO global, por cada sesión y por cada sentencia</title><content type='html'>&lt;div style="text-align: justify;"&gt;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".&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;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:&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Consumo de Redo de la ultima hora&lt;/li&gt;&lt;li&gt;Consumo de Redo por sesión&lt;/li&gt;&lt;li&gt;Consumo de Redo por Consultas (*)&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;div style="text-align: justify;"&gt;(*)  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.&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;span&gt;&lt;span style="font-weight: bold; "&gt;&lt;span class="Apple-style-span" style="font-size: 130%;"&gt;Para obtener consumo de redo global de la ultima hora (desde el ultimo snapshot de AWR) &lt;/span&gt;&lt;span class="Apple-style-span"&gt;(redo consumption by DB)&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;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&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;select round((t1.value-t2.value)/1024/1024,2) "Consumo_Redo(MB)"&lt;br /&gt;from&lt;br /&gt;(select value from v$sysstat where name = 'redo size') t1,&lt;br /&gt;(select value from dba_hist_sysstat&lt;br /&gt;where stat_name = 'redo size'&lt;br /&gt;and snap_id = (select max(snap_id) from dba_hist_sysstat)) t2&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;&lt;span style="font-weight: bold;"&gt;Para obtener consumo de redo por sesión  &lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="font-weight: bold; "&gt;&lt;span class="Apple-style-span"&gt;(redo consumption by session)&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;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&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;select se.INST_ID,&lt;br /&gt; se.SID,&lt;br /&gt; se.USERNAME,&lt;br /&gt; se.OSUSER,&lt;br /&gt; se.TERMINAL,&lt;br /&gt; se.PROGRAM,&lt;br /&gt; round(ss.value/1024/1024,2) "Redo Size(Mb)"&lt;br /&gt;from gv$session se,&lt;br /&gt;gv$sesstat ss,&lt;br /&gt;v$statname sn&lt;br /&gt;where se.SID = ss.SID&lt;br /&gt;and ss.STATISTIC# = sn.STATISTIC#&lt;br /&gt;and sn.NAME = 'redo size'&lt;br /&gt;and username is not null&lt;br /&gt;order by  "Redo Size(Mb)" desc&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;span&gt;&lt;span style="font-weight: bold; "&gt;&lt;span class="Apple-style-span" style="font-size: 130%;"&gt;Para obtener consumo de redo por sentencia &lt;/span&gt;&lt;span class="Apple-style-span"&gt;(redo consumption by sqlid/sentence)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;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) :&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;pre&gt;Rem&lt;br /&gt;Rem  setup_redo_usage.sql (Redo Usage by Sentence/SQLID)&lt;br /&gt;Rem&lt;br /&gt;Rem  NOMBRE&lt;br /&gt;Rem       setup_redo_usage.sql&lt;br /&gt;Rem&lt;br /&gt;Rem  DESCRIPCION&lt;br /&gt;Rem       Configura la programacion de un job para recolectar         &lt;br /&gt;Rem       información de uso de espacio de redo por sqlid.&lt;br /&gt;Rem&lt;br /&gt;Rem  NOTAS - REQUISITOS DE INSTALACION&lt;br /&gt;Rem       El usuario que ejecute el script debera tener quota&lt;br /&gt;Rem       suficiente sobre un tablespace auxiliar (ej:TOOLS)&lt;br /&gt;Rem       para poder almacenar la informacion de monitoreo generada&lt;br /&gt;Rem       cada 5".&lt;br /&gt;Rem       Es necesario otorgar privilegios de SELECT sobre las&lt;br /&gt;Rem       vistas dinamicas:&lt;br /&gt;Rem&lt;br /&gt;Rem       Conectado con sys hacer:&lt;br /&gt;Rem&lt;br /&gt;Rem       grant select on gv_$sesstat to &lt;usuario&gt;;&lt;br /&gt;Rem       grant select on v_$statname to &lt;usuario&gt;;&lt;br /&gt;Rem       grant select on gv_$session to &lt;usuario&gt;;&lt;br /&gt;Rem       grant select on gv_$sqlarea to &lt;usuario&gt;;&lt;br /&gt;Rem      &lt;br /&gt;Rem&lt;br /&gt;Rem  MODIFICADO (DD/MM/YY)&lt;br /&gt;Rem&lt;br /&gt;Rem&lt;br /&gt;Rem      Pablo A. Rovedo  06/01/11  -- Creado     v 1.0&lt;br /&gt;Rem&lt;br /&gt;&lt;br /&gt;-- Borra la tabla TBL_REDO_USAGE si existe&lt;br /&gt;&lt;br /&gt;drop table tmp$redo_usage&lt;br /&gt;/&lt;br /&gt;&lt;br /&gt;-- Crea la tabla de repositorio TBL_REDO_USAGE&lt;br /&gt;&lt;br /&gt;create table tmp$redo_usage&lt;br /&gt;(&lt;br /&gt;USERNAME     VARCHAR2(30),&lt;br /&gt;OSUSER       VARCHAR2(30),&lt;br /&gt;TERMINAL     VARCHAR2(30),&lt;br /&gt;PROGRAM      VARCHAR2(48),&lt;br /&gt;SQL_ID       VARCHAR2(13),&lt;br /&gt;SQL_FULLTEXT CLOB,&lt;br /&gt;REDO_SIZE    NUMBER,&lt;br /&gt;SNAP         DATE,&lt;br /&gt;INST_ID      NUMBER(1)&lt;br /&gt;)&lt;br /&gt;pctfree 0&lt;br /&gt;nologging&lt;br /&gt;/&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;-- Crea el procedimiento que recolecta la información de Alocación de&lt;br /&gt;-- espacio de redo&lt;br /&gt;&lt;br /&gt;create or replace procedure p_get_redo_usage&lt;br /&gt;is&lt;br /&gt;begin&lt;br /&gt;insert /*+ append */ into tmp$redo_usage&lt;br /&gt;select s.USERNAME,&lt;br /&gt;       s.OSUSER,&lt;br /&gt;       s.terminal,&lt;br /&gt;       s.PROGRAM,&lt;br /&gt;       s.SQL_ID,&lt;br /&gt;       sa.SQL_FULLTEXT,&lt;br /&gt;       round(ss.VALUE/1024/1024) redo_size,&lt;br /&gt;       sysdate,&lt;br /&gt;       s.inst_id&lt;br /&gt;from gv$sesstat ss,&lt;br /&gt;     v$statname sn,&lt;br /&gt;     gv$session s,&lt;br /&gt;     gv$sqlarea sa&lt;br /&gt;where s.sid = ss.sid&lt;br /&gt;  and s.inst_id = ss.inst_id&lt;br /&gt;  and sn.STATISTIC# = ss.STATISTIC#&lt;br /&gt;  and s.sql_id = sa.SQL_ID&lt;br /&gt;  and s.inst_id = sa.inst_id&lt;br /&gt;  and sn.name = 'redo size'&lt;br /&gt;  and s.username not in ('SYS','SYSTEM');&lt;br /&gt;commit;&lt;br /&gt;end;&lt;br /&gt;/&lt;br /&gt;&lt;br /&gt;-- Borra el job si ya existe&lt;br /&gt;&lt;br /&gt;begin&lt;br /&gt;dbms_scheduler.drop_job(job_name =&gt; 'J_SAVE_REDO_USAGE',&lt;br /&gt;                     force =&gt; true);&lt;br /&gt;end;&lt;br /&gt;/&lt;br /&gt;&lt;br /&gt;-- Crea un nuevo job&lt;br /&gt;&lt;br /&gt;begin&lt;br /&gt;dbms_scheduler.create_job(&lt;br /&gt;job_name =&gt; 'J_SAVE_REDO_USAGE'&lt;br /&gt;,job_type =&gt; 'PLSQL_BLOCK'&lt;br /&gt;,job_action =&gt; 'begin p_get_redo_usage; end;'&lt;br /&gt;,start_date =&gt; sysdate&lt;br /&gt;,end_date =&gt; sysdate+1/24  -- Recolecta durante 1 hora&lt;br /&gt;,repeat_interval =&gt; 'FREQ=SECONDLY;BYSECOND=0,5,10,15,20,25,30,35,40,45,50,55'&lt;br /&gt;,enabled =&gt; TRUE&lt;br /&gt;,comments =&gt; 'Almacena Informacion de Alocación de espacio de redo');&lt;br /&gt;end;&lt;br /&gt;/&lt;br /&gt;&lt;/usuario&gt;&lt;/usuario&gt;&lt;/usuario&gt;&lt;/usuario&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/pre&gt;&lt;div style="text-align: justify;"&gt;Luego de terminada la recolección (en el script se definió un intervalo&lt;br /&gt;de monitoreo de 1 hora) se puede ejecutar la siguiente consulta para ver&lt;br /&gt;los resultados:&lt;br /&gt;&lt;/div&gt;&lt;pre&gt;&lt;pre&gt;&lt;br /&gt;select sql_id,sum(redo_size) "redo_size(Mb)"&lt;br /&gt;from (select unique sql_id,&lt;br /&gt;       redo_size-lead(redo_size) over (partition by sql_id order by snap desc) redo_size&lt;br /&gt;from tmp$redo_usage)&lt;br /&gt;where redo_size is not null&lt;br /&gt;group by sql_id&lt;br /&gt;order by "redo_size(Mb)" desc&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5727738329548735676-8680828294825532982?l=oramdq.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://oramdq.blogspot.com/feeds/8680828294825532982/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://oramdq.blogspot.com/2011/01/consumo-de-redo-global-por-cada-sesion.html#comment-form' title='0 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/8680828294825532982'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/8680828294825532982'/><link rel='alternate' type='text/html' href='http://oramdq.blogspot.com/2011/01/consumo-de-redo-global-por-cada-sesion.html' title='Análisis de consumo de espacio REDO global, por cada sesión y por cada sentencia'/><author><name>Pablo A. Rovedo</name><uri>http://www.blogger.com/profile/02924687287216878757</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://3.bp.blogspot.com/_scsqOe7UOdc/SYDOV9ANLfI/AAAAAAAAAFQ/71M2XO1AZ1M/S220/DSC02067+(Small).JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5727738329548735676.post-8454061038233233963</id><published>2010-12-22T06:07:00.000-08:00</published><updated>2010-12-24T05:18:22.681-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Conceptos'/><category scheme='http://www.blogger.com/atom/ns#' term='CBO'/><category scheme='http://www.blogger.com/atom/ns#' term='Performance'/><title type='text'>Comparacion de Métodos y Tipos de  joins en Oracle</title><content type='html'>&lt;div style="text-align: justify;"&gt; Para armar el plan de ejecución el optimizador debe realizar las siguientes acciones básicas:&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;ol style="text-align: justify;"&gt;&lt;li&gt; Determinar el orden de evaluación de las tablas. &lt;/li&gt;&lt;li&gt; Determinar el método de join.&lt;/li&gt;&lt;li&gt;Determinar los tipos de accesos (access path, ej: full scan, rowid, index range, etc).&lt;/li&gt;&lt;li&gt; Determinar el orden de filtrado. &lt;/li&gt;&lt;/ol&gt;&lt;div style="text-align: justify;"&gt;&lt;br /&gt;Los 3 primeros forman la estructura de árbol que da soporte al plan de ejecución. El 4to define el caudal de datos que "fluye" por el árbol. En esta oportunidad solo voy a concentrarme en el punto 2, dejando los otros para futuras notas.&lt;br /&gt;&lt;br /&gt;Los joins se realizan siempre entre dos set de datos, si la sentencias tuviera mas de dos tablas se determinan la dos primeras tablas a joinear y el resultado se joinea con la siguiente tabla, ese resultado se joinea con la siguiente tabla y asi siguiendo.&lt;br /&gt;&lt;br /&gt;Los métodos de join mas comunes son:&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;ul style="text-align: justify;"&gt;&lt;li&gt;  NESTED LOOP JOIN&lt;/li&gt;&lt;li&gt;  SORT MERGE JOIN&lt;/li&gt;&lt;li&gt;  HASH JOIN&lt;/li&gt;&lt;li&gt;  CARTESIAN JOIN&lt;/li&gt;&lt;/ul&gt;&lt;div style="text-align: justify;"&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Descripción de NESTED LOOP JOIN&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Los dos set de datos procesados por nested loop (NL) se llaman outer loop e inner loop. El outer loop es ejecutado una sola vez y el inner loop una vez por cada registro retornado por el outer loop. Las principales caracteristicas de NL son:&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;ul style="text-align: justify;"&gt;&lt;li&gt;Son la mejor opción cuando se requiere obtener la primera fila lo antes posible, de esta forma no es necesario tener que procesar todos los datos para comenzar a retornar resultados. Esto es muy performante, por ejemplo, para aplicaciones front-end que usan paginación.&lt;/li&gt;&lt;li&gt;Permiten aprovechar los filtros y condiciones de joins usando los indices disponibles.&lt;/li&gt;&lt;li&gt;Se pueden usar con cualquier tipo de joins. &lt;/li&gt;&lt;/ul&gt;&lt;div style="text-align: justify;"&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Descripción de HASH JOIN&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Los dos set de datos procesados por hash join (HJ) son build input y probe input. Con el build input se construye en memoria (o en tablespace temporal si no hubiera suficiente memoria fisica disponible) una tabla de hash. Una vez que se construyó la build input se comienza a procesar usando para cada registro de la probe input la tabla de hash de modo de comparar si se satisface o no la condición de join.  Las principales caracteristicas de HJ son:&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;ul style="text-align: justify;"&gt;&lt;li&gt;La tabla de hash usualmente es contruida usando el set de datos mas pequeño.&lt;/li&gt;&lt;li&gt;No todos los tipos de joins pueden usarse, por ejemplo los theta joins y cross joins no son soportados.&lt;/li&gt;&lt;li&gt;Para que se comiencen a retornar las filas la tabla de hash debe estar creada y procesada.&lt;/li&gt;&lt;li&gt;HJ no puede aplicar condiciones de joins usando indices.&lt;/li&gt;&lt;/ul&gt;&lt;div style="text-align: justify;"&gt;&lt;span style="font-weight: bold;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Descripción de SORT MERGE JOIN&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Los dos set de datos procesado por el merge join (MJ)  son leidos y ordenados de acuerdo a las columnas referenciadas en la condición de join. Una vez que los dos set estan ordenados son mezclados (merge).  El ordenamiento se realiza en memoria siempre y cuando la memoria fisica sea suficiente, sino alcanza la memoria (pga) se deberá usar espacio temporal como soporte lo cual, como es esperable, ralentizará las operaciones.  Las principales caracteristicas de MJ son:&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;ul style="text-align: justify;"&gt;&lt;li&gt;Ambos data set deben ser ordenados antes del merge&lt;/li&gt;&lt;li&gt;La primera fila del result set recien es retornada cuando comienza el merge. &lt;/li&gt;&lt;li&gt;Todos los tipos de joins son soportados.&lt;/li&gt;&lt;/ul&gt;&lt;div style="text-align: justify;"&gt;&lt;span style="font-size:130%;"&gt;&lt;span style="font-weight: bold;"&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;&lt;span style="font-weight: bold;"&gt;Tipos de Joins&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Existen dos sintaxis posibles para usar con joins:&lt;br /&gt;&lt;br /&gt;        SQL-ANSI-86&lt;br /&gt;        SQL-ANSI-92&lt;br /&gt;&lt;br /&gt;La primera es la que uso en general, y es la mas común, la segunda es mas nueva y es standard  para otros motores de base de datos, es mas común para la nuevas generaciones de desarrolladores y dbas o para los que vengan de usar sql server . Es, además mas clara porque separa los filtros de los joins, lo cual es mas sencillo para leer e interpretar. Ahora voy a hacer un breve repaso de los tipos de joins con ejemplos en la dos notaciones:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Cross Join&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Tambien llamado producto cartesiano. En general se usa cuando no se especifican los joins para algunas tablas. Tambien lo he visto en ciertos planes particulares donde es la mejor opción , aunque es muy raro&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;select emp.ename,dept.dname&lt;br /&gt;from emp, dept&lt;br /&gt;&lt;br /&gt;select emp.ename,dept.dname&lt;br /&gt;from emp CROSS JOIN dept&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Theta Join &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Tambien llamados inner join, y retorna solo las filas que satisfacen una condición de join&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;select emp.enam, salgrade.grade&lt;br /&gt;from emp, salgrade&lt;br /&gt;where emp.sal between salgrade.local and salgrade.hisal&lt;br /&gt;&lt;br /&gt;select emp.ename, salgrade.grade&lt;br /&gt;from emp INNER JOIN salgrade on emp.sal between salgrade.losal and salgrade.hisal&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Equi Join&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;div style="text-align: justify;"&gt;Tambien llamado natural join, es un caso especial de theta join donde solo se usan operadores&lt;br /&gt;de igualdad para las condiciones de join&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;select emp.ename, dept.dname&lt;br /&gt;from emp, dept&lt;br /&gt;where emp.deptno = dept.deptno&lt;br /&gt;&lt;br /&gt;select emp.ename, dept.dname&lt;br /&gt;from emp NATURAL JOIN dept on emp.deptno = dept.deptno&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Self Join &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Son un caso especial de theta join donde la tabla joineada es la misma.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;select emp.ename,mgr.ename&lt;br /&gt;from emp, emp mgr&lt;br /&gt;where emp.mgr = mgr.empno&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;select emp.ename, mgr.ename&lt;br /&gt;from emp JOIN emp mgr on emp.mgr = mgr.empno&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Outer Join&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Los outer join extienden el result set de los theta joins.  Con este tipo de join todas las filas de una de la tablas involucradas son retornadas aunque no matcheen con las columnas de  join de la otra tabla, retornando NULL en las columnas de los registros de la tabla que no matchea. Oracle usa una sintaxis propia pero lo recomendable es usar la sintaxis ansi-92 ya que es portable a otros motores de base de datos.&lt;br /&gt;&lt;br /&gt;Por ejemplo, para ver la cantidad de empleados por departamento, considerando tambien los departamentos que no tienen ningun empleado:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;select dept.dname,count(emp.ename)&lt;br /&gt;from  emp,  dept&lt;br /&gt;where dept.deptno = emp.DEPTNO (+)&lt;br /&gt;group by dept.dname&lt;br /&gt;&lt;br /&gt;select dept.dname,count(emp.ename)&lt;br /&gt;from dept LEFT OUTER JOIN emp on (dept.deptno = emp.DEPTNO)&lt;br /&gt;group by dept.dname&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Con la nueva sintaxis tambien se puede usar RIGHT OUTER JOIN y FULL OUTER JOIN.&lt;br /&gt;&lt;br /&gt;A partir de Oracle 10g es posible usar un nuevo tipo de join ( o subtipo) llamado partitioned outer join. Este tipo de join a priori pareceria estar relacionado con tablas particionadas pero no,  en este caso, el concepto de particionado es que los datos se dividen en subset durante la ejecución&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;select dept.dname, count(emp.empno)&lt;br /&gt;from dept LEFT JOIN emp PARTITION BY (emp.job)  ON emp.deptno = dept.deptno&lt;br /&gt;group by dept.dname&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Semi Join&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Este tipo de join entre dos tablas retorna solo las filas de una de las tablas cuyas columas de join existen en la otra tabla.&lt;br /&gt;&lt;br /&gt;Por ejemplo para ver que empleados tienen bonus:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;select *&lt;br /&gt;from  scott.emp emp&lt;br /&gt;where exists (select null from scott.bonus bon&lt;br /&gt;            where emp.EMPNO = bon.ename)&lt;br /&gt;         &lt;br /&gt;select *&lt;br /&gt;from  scott.emp emp&lt;br /&gt;where empno in (select empno from scott.bonus bon)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Anti Join&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Este tipo de join entre dos tablas retorna solo las filas de una de las tablas cuyas columnas de join NO existen en la otra tabla&lt;br /&gt;&lt;br /&gt;Por ejemplo para consultar los empleados que no tienen bonus:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;select *&lt;br /&gt;from  scott.emp emp&lt;br /&gt;where not exists (select null from scott.bonus bon&lt;br /&gt;            where emp.EMPNO = bon.ename)&lt;br /&gt;         &lt;br /&gt;select *&lt;br /&gt;from  scott.emp emp&lt;br /&gt;where empno not in (select empno from scott.bonus bon)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Una vez repasados los tipos de joins retomemos los métodos de joins y veamos con algunos ejemplos como se arman los planes según cada método:&lt;br /&gt;&lt;br /&gt;Como siempre voy a crear el entorno para poder probar y si alguien quiere testearlo en su propio ambiente puede hacerlo:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;-- Creo tabla T1&lt;br /&gt;create table t1&lt;br /&gt;as&lt;br /&gt;select rownum c1,&lt;br /&gt;     trunc(dbms_random.value(1,100)) c2,&lt;br /&gt;     dbms_random.string('a',100) c3&lt;br /&gt;from dual&lt;br /&gt;connect by rownum &lt;= 1000000  -- Creo tabla T2 create table t2 as select rownum c1,        trunc(dbms_random.value(1,100000)) c2,        dbms_random.string('a',100) c3 from dual connect by rownum &lt;= 2000000  -- Creo un indice para la tabla T2 create index t2_idx on t2(c2)  -- Recolecto estadisticas para los segmentos creados: begin     dbms_stats.gather_table_stats(ownname =&gt; user,tabname =&gt; 'T1',cascade =&gt; true);&lt;br /&gt;  dbms_stats.gather_table_stats(ownname =&gt; user,tabname =&gt; 'T2',cascade =&gt; true);&lt;br /&gt;end;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Ahora voy a mostrar cada método de join, obviamente lo voy a forzar con hints para hacerlo mas sencillo:&lt;br /&gt;&lt;br /&gt;Forzamos para que se use NESTED LOOP JOIN:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;select  /*+ leading(t1) use_nl(t2) index(t2)  */ count(1)&lt;br /&gt;from t1, t2&lt;br /&gt;where t1.c2 = t2.c2&lt;br /&gt;and t1.c3 &gt; 'zzz'&lt;br /&gt;&lt;br /&gt;Plan hash value: 3705558160&lt;br /&gt;&lt;br /&gt;------------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation           | Name   | Rows  | Bytes | Cost (%CPU)| Time     |&lt;br /&gt;------------------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT    |        |     1 |   109 |  3602   (1)| 00:01:19 |&lt;br /&gt;|   1 |  SORT AGGREGATE     |        |     1 |   109 |            |          |&lt;br /&gt;|   2 |   NESTED LOOPS      |        |    21 |  2289 |  3602   (1)| 00:01:19 |&lt;br /&gt;|*  3 |    TABLE ACCESS FULL| T1     |     1 |   104 |  3600   (1)| 00:01:19 |&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;|*  4 |    INDEX RANGE SCAN | T2_IDX |    21 |   105 |     2   (0)| 00:00:01 |&lt;/span&gt;&lt;br /&gt;------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt; 3 - filter("T1"."C3"&gt;'zzz')&lt;br /&gt; 4 - access("T1"."C2"="T2"."C2")&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Forzamos para que se use MERGE JOIN:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;select /*+ ordered use_merge(t2) */ count(1)&lt;br /&gt;from t1, t2&lt;br /&gt;where t1.c2 = t2.c2&lt;br /&gt;and t1.c3 &gt; 'zzz'&lt;br /&gt;&lt;br /&gt;Plan hash value: 1164406001&lt;br /&gt;&lt;br /&gt;------------------------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation               | Name   | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |&lt;br /&gt;------------------------------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT        |        |     1 |   109 |       | 10796   (2)| 00:03:55 |&lt;br /&gt;|   1 |  SORT AGGREGATE         |        |     1 |   109 |       |            |          |&lt;br /&gt;|   2 |   MERGE JOIN            |        |    21 |  2289 |       | 10796   (2)| 00:03:55 |&lt;br /&gt;|   3 |    SORT JOIN            |        |     1 |   104 |       |  3601   (1)| 00:01:19 |&lt;br /&gt;|*  4 |     TABLE ACCESS FULL   | T1     |     1 |   104 |       |  3600   (1)| 00:01:19 |&lt;br /&gt;|*  5 |    SORT JOIN            |        |  1997K|  9754K|    45M|  7195   (2)| 00:02:37 |&lt;br /&gt;|&lt;span style="font-weight: bold;"&gt;   6 |     INDEX FAST FULL SCAN| T2_IDX |  1997K|  9754K|       |  1007   (2)| 00:00:22 |&lt;/span&gt;&lt;br /&gt;------------------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt; 4 - filter("T1"."C3"&gt;'zzz')&lt;br /&gt; 5 - access("T1"."C2"="T2"."C2")&lt;br /&gt;     filter("T1"."C2"="T2"."C2")&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Forzamos para que se use HASH JOIN:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;select /*+ leading(t1) use_hash(t2) */ t1.*&lt;br /&gt;from t1, t2&lt;br /&gt;where t1.c2 = t2.c2&lt;br /&gt;and t1.c3 &gt; 'zzz'&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Plan hash value: 442409572&lt;br /&gt;&lt;br /&gt;---------------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation              | Name   | Rows  | Bytes | Cost (%CPU)| Time     |&lt;br /&gt;---------------------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT       |        |     1 |   109 |  4620   (2)| 00:01:41 |&lt;br /&gt;|   1 |  SORT AGGREGATE        |        |     1 |   109 |            |          |&lt;br /&gt;|*  2 |   HASH JOIN            |        |    21 |  2289 |  4620   (2)| 00:01:41 |&lt;br /&gt;|*  3 |    TABLE ACCESS FULL   | T1     |     1 |   104 |  3600   (1)| 00:01:19 |&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;|   4 |    INDEX FAST FULL SCAN| T2_IDX |  1997K|  9754K|  1007   (2)| 00:00:22 |&lt;/span&gt;&lt;br /&gt;---------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt; 2 - access("T1"."C2"="T2"."C2")&lt;br /&gt; 3 - filter("T1"."C3"&gt;'zzz')&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Comparando los 3 planes para cada método se ve que solo con NL usa index range en lugar de usar un full scan por el indice. Los tiempos de NL son los mejores según la estimación del plan. Ejecutando cada uno de los 3 queries se puede ver que dicha estimación coincide con la realidad y que NL es el mas rapido. Esto se da porque tanto con HJ como con MJ no se puede usar el indice para buscar las coincidencias sobre la tabla T2 basado en los valores retornados por la tabla T1.  Con NL se aprovecha dicha información para acceder mas puntualmente, via el indice. Cuanto menor sea la selectividad (o mas fuerte)  el método NL tendrá mayor ventaja sobre los otros dos.&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5727738329548735676-8454061038233233963?l=oramdq.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://oramdq.blogspot.com/feeds/8454061038233233963/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://oramdq.blogspot.com/2010/12/comparacion-de-metodos-y-tipos-de-joins.html#comment-form' title='0 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/8454061038233233963'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/8454061038233233963'/><link rel='alternate' type='text/html' href='http://oramdq.blogspot.com/2010/12/comparacion-de-metodos-y-tipos-de-joins.html' title='Comparacion de Métodos y Tipos de  joins en Oracle'/><author><name>Pablo A. Rovedo</name><uri>http://www.blogger.com/profile/02924687287216878757</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://3.bp.blogspot.com/_scsqOe7UOdc/SYDOV9ANLfI/AAAAAAAAAFQ/71M2XO1AZ1M/S220/DSC02067+(Small).JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5727738329548735676.post-3473182238547046615</id><published>2010-12-15T04:31:00.001-08:00</published><updated>2010-12-15T08:49:13.870-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Diagnóstico'/><category scheme='http://www.blogger.com/atom/ns#' term='Conceptos'/><category scheme='http://www.blogger.com/atom/ns#' term='CBO'/><category scheme='http://www.blogger.com/atom/ns#' term='Procesamiento'/><category scheme='http://www.blogger.com/atom/ns#' term='SQL'/><category scheme='http://www.blogger.com/atom/ns#' term='Performance'/><title type='text'>Cuando una consulta utiliza un indice, pero no el mejor indice posible</title><content type='html'>&lt;div style="text-align: justify;"&gt;Muchas veces me preguntaron por que una consulta no responde en un tiempo adecuado cuando el plan de ejecución muestra que se esta utilizando un indice. La respuesta en ciertos casos es muy sencilla y se debea a que el indice no es lo mas selectivo posible, no filtra todo lo que podria filtrar. Decir que una consulta usa un plan que accede por un indice no es suficiente para asegurar que se haya encontrado el camino mas eficiente. Para mostrarles un poco de que estoy hablando voy a armar un caso, un tanto trivial pero no por eso menos ilustrativo, para que se entienda la idea.&lt;br /&gt;&lt;br /&gt;Voy a crear una tabla T con 3 columnas. La columna COL1 va a tener 1000 valores distintos y la columna COL2 va a tener  10 valores posibles. Además agrego la columna COL3 de relleno&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;create table t&lt;span style="font-family:monospace;"&gt;&lt;br /&gt;&lt;/span&gt;as &lt;br /&gt;select mod(rownum,1000) col1,&lt;span style="font-family:monospace;"&gt;&lt;br /&gt;&lt;/span&gt;       mod(rownum,100000) col2,         &lt;br /&gt;      dbms_random.string('a',50) col3&lt;br /&gt;from dual&lt;br /&gt;connect by rownum &lt;= 1000000&lt;br /&gt;&lt;div style="text-align: justify;"&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Una vez creada la tabla voy a crear un indice por COL1 y recolecto las estadisticas para la tabla y para el indice:&lt;br /&gt;&lt;/div&gt;&lt;pre&gt;&lt;br /&gt;create index t_idx on t (col1)&lt;br /&gt;&lt;br /&gt;begin&lt;br /&gt;dbms_stats.gather_table_stats(ownname =&gt; user,tabname =&gt; 'T',cascade =&gt; true);&lt;br /&gt;end;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;div style="text-align: justify;"&gt;Ejecuto la siguiente consulta y extraigo el plan usando DBMS_XPLAN.DISPLAY_CURSOR para que el plan sea mas detallado (en una nota futura voy a usar el mismo método para mostrar como ver como se "confunde" el optimizador cuando no se dispone de las estadisticas adecuadas):&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;select /*+ gather_plan_statistics */ * from t&lt;br /&gt;where col1 = 9&lt;br /&gt;and col2 = 9&lt;br /&gt;&lt;br /&gt;select * from table (dbms_xplan.display_cursor(null,null,'ALLSTATS LAST'));&lt;br /&gt;&lt;br /&gt;PLAN_TABLE_OUTPUT&lt;br /&gt;----------------------------------------------------------------------------------------------------&lt;br /&gt;SQL_ID  25p2md7bszhj6, child number 0&lt;br /&gt;-------------------------------------&lt;br /&gt;select /*+ gather_plan_statistics */ * from t where col1 = 9   and col2&lt;br /&gt;= 9&lt;br /&gt;&lt;br /&gt;Plan hash value: 1020776977&lt;br /&gt;&lt;br /&gt;-----------------------------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation                   | Name  | Starts | E-Rows | A-Rows |   A-Time   | Buffers |&lt;br /&gt;-----------------------------------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT            |       |      1 |        |     10 |00:00:00.01 |    1006 |&lt;br /&gt;|*  1 |  TABLE ACCESS BY INDEX ROWID| T     |      1 |      1 |     10 |00:00:00.01 |    1006 |&lt;br /&gt;|*  2 |   INDEX RANGE SCAN          | T_IDX |      1 |   1000 |   1000 |00:00:00.01 |       6 |&lt;br /&gt;-----------------------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;1 - filter("COL2"=9)&lt;br /&gt;2 - access("COL1"=9)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;div style="text-align: justify;"&gt;En el paso 2 del plan se observa que la estimación fue buena (E-Rows=A-Rows)  pero la cantidad filtrada fue 1ooo filas, cuando realmente se deberian haber filtrado 10, como se ve en el paso 1 (A-Rows)&lt;br /&gt;&lt;br /&gt;(Aclaración: A-Rows es la cantidad de filas reales y E-Rows es la cantidad de filas estimada)&lt;br /&gt;&lt;br /&gt;Ahora voy a eliminar el indice T_IDX y voy a crear otro con el mismo nombre pero indexando por COL1 y COL2. Veamos el nuevo plan:&lt;br /&gt;&lt;/div&gt;&lt;pre&gt;&lt;br /&gt;drop index t_idx&lt;br /&gt;&lt;br /&gt;create index t_idx on t (col1,col2)&lt;br /&gt;&lt;br /&gt;select /*+ gather_plan_statistics 2 */ * from t&lt;br /&gt;where col1 = 9&lt;br /&gt;and col2 = 9&lt;br /&gt;&lt;br /&gt;select * from table (dbms_xplan.display_cursor(null,null,'ALLSTATS LAST'));&lt;br /&gt;&lt;br /&gt;PLAN_TABLE_OUTPUT&lt;br /&gt;----------------------------------------------------------------------------------------------------&lt;br /&gt;SQL_ID  25p2md7bszhj6, child number 0&lt;br /&gt;-------------------------------------&lt;br /&gt;select /*+ gather_plan_statistics */ * from t where col1 = 9   and col2&lt;br /&gt;= 9&lt;br /&gt;&lt;br /&gt;Plan hash value: 1020776977&lt;br /&gt;&lt;br /&gt;-----------------------------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation                   | Name  | Starts | E-Rows | A-Rows |   A-Time   | Buffers |&lt;br /&gt;-----------------------------------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT            |       |      1 |        |     10 |00:00:00.01 |      14 |&lt;br /&gt;|   1 |  TABLE ACCESS BY INDEX ROWID| T     |      1 |     10 |     10 |00:00:00.01 |      14 |&lt;br /&gt;|*  2 |   INDEX RANGE SCAN          | T_IDX |      1 |     10 |     10 |00:00:00.01 |       4 |&lt;br /&gt;-----------------------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;2 - access("COL1"=9 AND "COL2"=9)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;div style="text-align: justify;"&gt;Ahora el paso 2 retorna al paso 1 solo 10 filas, que es la cantidad de filas total retornada por la consulta.  Observar que la cantidad de buffers utilizado es bastante inferior al caso anterior.&lt;br /&gt;Esto demuestra que el nuevo indice fue 100 veces mas selectivo y por lo tanto se necesitaron menos recursos, en consecuencia menos tiempo de procesamiento,  para obtener la misma salida.&lt;br /&gt;&lt;br /&gt;La moraleja es que nunca hay que conformarse con solo verificar que la sentencia accede por un indice y profundizar en el análisis sobre el esquema actual de indexación comprobando si es el mejor indice posible o si se puede buscar alguna otra combinación mas eficiente.&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5727738329548735676-3473182238547046615?l=oramdq.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://oramdq.blogspot.com/feeds/3473182238547046615/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://oramdq.blogspot.com/2010/12/cuando-mi-consulta-utiliza-un-indice.html#comment-form' title='0 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/3473182238547046615'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/3473182238547046615'/><link rel='alternate' type='text/html' href='http://oramdq.blogspot.com/2010/12/cuando-mi-consulta-utiliza-un-indice.html' title='Cuando una consulta utiliza un indice, pero no el mejor indice posible'/><author><name>Pablo A. Rovedo</name><uri>http://www.blogger.com/profile/02924687287216878757</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://3.bp.blogspot.com/_scsqOe7UOdc/SYDOV9ANLfI/AAAAAAAAAFQ/71M2XO1AZ1M/S220/DSC02067+(Small).JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5727738329548735676.post-2897447277631780178</id><published>2010-11-24T09:53:00.000-08:00</published><updated>2010-12-01T03:55:47.959-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Procesamiento'/><category scheme='http://www.blogger.com/atom/ns#' term='SQL'/><category scheme='http://www.blogger.com/atom/ns#' term='Performance'/><title type='text'>Como realizar update/delete masivos en forma efectiva</title><content type='html'>&lt;div style="text-align: justify;"&gt;     En esta nota voy a mostrarles un método efectivo para modificar o eliminar una gran cantidad de filas sobre una tabla grande. En general las tablas voluminosas se encuentran particionadas para lograr escalar en forma natural. El particionamiento principalmente provee 3 tipos de beneficios: 1) mejora la performance, 2) facilita la administración y mantenimiento y 3) incrementa la disponibilidad de los datos. Resolver una consulta usando como tabla subyacente particionada puede verse de la misma forma que resolver un problema dividiendolo en partes. La conocida premisa: divide y conquistaras es el principal objetivo detrás de particionar.&lt;br /&gt;&lt;br /&gt;Desde que se introdujo el feature de partitioning (Oracle 8) se ha ampliado notablemente el set de operaciones posibles sobre tablas e indices para dar soporte y manejar las tablas/indices particionados. Con cada nuevo release se  fueron agregando distintas opciones, métodos de particionado y operaciones para manipulación de segmentos. Los distintos features introducidas en cada release son:&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;font-family:arial;font-size:100%;"  &gt;&lt;span&gt;Oracle 8 (1997)&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Partition Pruning (*)&lt;/li&gt;&lt;li&gt;Range Partitioning (incluye operaciones ADD, DROP, RENAME, TRUNCATE, MODIFY, MOVE, SPLIT y EXCHANGE)&lt;/li&gt;&lt;/ul&gt;&lt;pre&gt;&lt;pre&gt;&lt;br /&gt;&lt;span style="font-weight: bold;font-family:arial;font-size:100%;"  &gt;&lt;span&gt;Oracle 8i (1999)&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;/pre&gt;&lt;ul&gt;&lt;li&gt;    Particionamiento Hash&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Particionamiento compuesto: range/hash&lt;br /&gt;&lt;/li&gt;&lt;li&gt;    Se agregó la operación MERGE&lt;/li&gt;&lt;/ul&gt;&lt;span style="font-weight: bold;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold;font-size:100%;" &gt;&lt;span style="font-family:arial;"&gt;Oracle 9i R2 (2002)&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;List Partitioning&lt;/li&gt;&lt;li&gt;Particionamiento compuesto: Range/List&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Cláusula UPDATE GLOBAL INDEXES&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;pre&gt;&lt;pre&gt;&lt;span style="font-weight: bold;font-family:arial;font-size:130%;"  &gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="font-weight: bold;font-family:arial;font-size:100%;"  &gt;Oracle 10g R1 (2004)&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;/pre&gt;&lt;ul&gt;&lt;li&gt;   Indices globales particionados por Hash y List&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;span style="font-weight: bold;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="font-weight: bold;font-family:arial;" &gt;Oracle 10g R2 (2005)&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt; Se incremento el limite de particiones/subparticiones de 65k a 4M&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;span style="font-weight: bold;font-family:arial;" &gt;Oracle 11g R1 (2007)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Particionamiento compuesto: range-range, list-range, list-list y list-hash. &lt;/li&gt;&lt;li&gt;Se agregó particionamiento por intervalo, por referencia y de sistema.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;span style="font-weight: bold;font-family:arial;" &gt;Oracle 11g R2 (2009)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Columnas virtuales como primary key para tablas particionadas referenciadas.&lt;/li&gt;&lt;li&gt;Indices particionados por sistema para tablas particionadas por lista.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;&lt;div style="text-align: justify;"&gt;Como se puede ver, practicamente en cada nuevo release hubo algún agregado de  nueva funcionalidad. Sin embargo, a mi entender, el principal feature existe desde el primer release con partitioning (1997). Me refiero al partition pruning o poda de partición, que posibilita que el optimizador (siempre hablando de CBO)  elija en forma automática, precisa y transparente la partición o particiones donde se encuentra los datos requeridos. Esto permite segmentar los datos y solo procesar los que nos interesan, sin tener que agregar ninguna inteligencia adicional en el código de aplicación.&lt;br /&gt;&lt;br /&gt;Con respecto a las operaciones, la gran mayoria existen desde Oracle 8, solo se agregó tiempo después el MERGE. Una operación muy interensante es EXCHANGE, con la cual se puede intercambiar una tabla sin particionar con una partición. Justamente es esta la operación que voy a usar para proponer una alternativa rapida para cambiar o borrar gran cantidad de filas sobre tablas particionadas.  A continuación, somo suelo hacer,  voy a mostrar los pasos en detalle y comparar los tiempos y uso de recursos:&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;Voy a crear una tabla T  particionada por lista con 3 particiones&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;create table t(c1 int,c2 varchar2(10),&lt;br /&gt;&lt;pre&gt;c3 date,&lt;br /&gt;c4 char(1))&lt;br /&gt;partition by list (c4)&lt;br /&gt;(&lt;br /&gt;partition t_a values ('A') ,&lt;br /&gt;partition t_b values ('B') ,&lt;br /&gt;partition t_c values ('C')&lt;br /&gt;);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/pre&gt;&lt;div style="text-align: justify;"&gt;Ahora voy a insertar 10M de filas distribuidas en forma arbitraria sobre las particiones:&lt;br /&gt;&lt;/div&gt;&lt;pre&gt;&lt;pre&gt;&lt;br /&gt;insert into t&lt;br /&gt;select rownum,&lt;br /&gt;dbms_random.string('a',10),&lt;br /&gt;sysdate-dbms_random.value(-100,100),&lt;br /&gt;chr(trunc(dbms_random.value(65,68)))&lt;br /&gt;from dual&lt;br /&gt;connect by rownum &lt;= 10000000;                &lt;/pre&gt;&lt;br /&gt;&lt;/pre&gt;&lt;div style="text-align: justify;"&gt;Inserto 5M de filas sobre la partición en la que voy a trabajar para tener mas filas:&lt;br /&gt;&lt;/div&gt;&lt;pre&gt;&lt;pre&gt;&lt;br /&gt;insert into t&lt;br /&gt;select rownum+10000000,          &lt;br /&gt;dbms_random.string('a',10),&lt;br /&gt;sysdate-dbms_random.value(-100,100),&lt;br /&gt;'A'  from dual&lt;br /&gt;connect by rownum &lt;= 5000000;         &lt;/pre&gt;&lt;br /&gt;&lt;/pre&gt;&lt;div style="text-align: justify;"&gt;Luego de cargados todos los valores se confirman (commit) y luego se recolectan estadisticas.&lt;br /&gt;Veamos el plan para una consulta que cuenta filas sobre la partición 1 (t_a):&lt;br /&gt;&lt;/div&gt;&lt;pre&gt;&lt;pre&gt;&lt;br /&gt;explain plan for&lt;br /&gt;select count(1)&lt;br /&gt;from t where c2 &gt; 'R' and c4 = 'A';&lt;br /&gt;&lt;br /&gt;select * from table(dbms_xplan.display);&lt;br /&gt;&lt;br /&gt;PLAN_TABLE_OUTPUT&lt;br /&gt;---------------------------------------------------------------------------------------------------&lt;br /&gt;Plan hash value: 2901716037&lt;br /&gt;&lt;br /&gt;-----------------------------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation              | Name | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |&lt;br /&gt;-----------------------------------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT       |      |     1 |    13 |  8455   (3)| 00:03:04 |       |       |&lt;br /&gt;|   1 |  SORT AGGREGATE        |      |     1 |    13 |            |          |       |       |&lt;br /&gt;|   2 |   PARTITION LIST SINGLE|      |  5588K|    69M|  8455   (3)| 00:03:04 |     1 |     1 |&lt;br /&gt;|*  3 |    TABLE ACCESS FULL   | T    |  5588K|    69M|  8455   (3)| 00:03:04 |     1 |     1 |&lt;br /&gt;-----------------------------------------------------------------------------------------------&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/pre&gt;&lt;div style="text-align: justify;"&gt;Claramente se observa que el optimizador solo accedió la partición 1. Ejecutando la consulta vemos que la estimación del optimizador fue buena:&lt;br /&gt;&lt;/div&gt;&lt;pre&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;select count(1)&lt;br /&gt;from t&lt;br /&gt;where c4 = 'A' and c2 &gt; 'R';&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;COUNT(1)&lt;br /&gt;----------&lt;br /&gt;5610297&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/pre&gt;&lt;div style="text-align: justify;"&gt;El total de filas de la partición es:&lt;br /&gt;&lt;/div&gt;&lt;pre&gt;&lt;pre&gt;&lt;br /&gt;select count(1)&lt;br /&gt;from t&lt;br /&gt;where c4 = 'A' ;&lt;br /&gt;&lt;br /&gt;COUNT(1)&lt;br /&gt;----------&lt;br /&gt;8333946&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/pre&gt;&lt;div style="text-align: justify;"&gt;En este punto, ya tenemos una partición con mas de 8.3M de filas de las cuales vamos a modificar 5.6M, lo cual es mas del 67%.&lt;br /&gt;Primero voy a testear un update normal sobre la tabla T para luego realizar la comparativa con la misma modificación pero usando otro enfoque mas eficiente.&lt;br /&gt;&lt;/div&gt;&lt;pre&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;update t&lt;br /&gt;set c3 = c3+1&lt;br /&gt;where c4 = 'A'&lt;br /&gt;and c2 &gt; 'R'&lt;br /&gt;&lt;br /&gt;5610297 filas actualizadas.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Transcurrido: 00:04:45.37&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/pre&gt;&lt;div style="text-align: justify;"&gt;La modificación demoró 4' 45". Pensemos que la base de datos debe mantener la consistencia para garantizar la lectura consistente (mediante el UNDO) y persistir los cambios para poder recuperarse si un evento de falla ocurre durante la modificación (REDO). Estos mecanismos provocan que los tiempos se incrementen y se genere información adicional.&lt;br /&gt;&lt;br /&gt;Revisemos cuanto espacio de UNDO y REDO se necesitó para realizar el update:&lt;br /&gt;&lt;/div&gt;&lt;pre&gt;&lt;pre&gt;&lt;br /&gt;&lt;pre&gt;select 'REDO_SIZE',&lt;br /&gt;round(ms.value/1024/1024) value&lt;br /&gt;from v$mystat ms,&lt;br /&gt;v$statname sn&lt;br /&gt;where ms.STATISTIC# = sn.STATISTIC#&lt;br /&gt;and sn.NAME = 'redo size'&lt;br /&gt;union all&lt;br /&gt;SELECT 'UNDO_SIZE',&lt;br /&gt;t.used_ublk*8/1024 value&lt;br /&gt;FROM v$transaction t, v$session s&lt;br /&gt;WHERE t.addr = s.taddr&lt;br /&gt;AND s.audsid = userenv('sessionid')&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;REDO_SIZE  2489 Mb&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;UNDO_SIZE   885 Mb&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/pre&gt;&lt;div style="text-align: justify;"&gt;&lt;div style="text-align: justify;"&gt;Para modificar 5.3M se necesitaron 2489Mb de redo y 885Mb de undo!!!. En el ejemplo, la tabla no tiene indices. Si tuviera indices y la columna modificada sea parte de las columnas de indexación se generaría mas redo y undo, y además la sentencia tendría que actualizar los indices por cada fila modificada lo cual provocaría que el update demore bastante mas. Si el procesamiento masivo fuera un delete en lugar de un update, se generará mas undo (el delete es la operación dml que mas undo genera) y se tendrá que mantener balanceados los indices, lo cual implica mas tiempo de procesamiento.&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;Existe una forma mas sencilla de realizar el update usando la operación estrella de partitioning: EXCHANGE. Antes de usar el exchange tenemos que crear una tabla auxiliar (T_A) y para acelerar la creación configuro la tabla como nologging e inserto en forma directa usando el hint APPEND.&lt;br /&gt;&lt;/div&gt;&lt;pre&gt;&lt;pre&gt;create table t_a_aux nologging as&lt;br /&gt;select /*+ APPEND */&lt;br /&gt;c1,&lt;br /&gt;c2,&lt;br /&gt;case when (c2&gt;'R') then c3+1&lt;br /&gt;else c3 end c3,&lt;br /&gt;c4&lt;br /&gt;from t&lt;br /&gt;where c4 = 'A'&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Transcurrido: 00:00:22.04&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/pre&gt;&lt;div style="text-align: justify;"&gt;Solo se necesitaron 22" para insertar la filas en la tabla auxiliar. Con la función DECODE o CASE realizo el cambio simulando el update. Ahora solo resta realizar el intercambio entre la tabla auxiliar y la partición t_a con la operación EXCHANGE:&lt;br /&gt;&lt;/div&gt;&lt;pre&gt;&lt;pre&gt;&lt;pre&gt;&lt;br /&gt;ALTER TABLE t&lt;br /&gt;EXCHANGE PARTITION t_a&lt;br /&gt;WITH table t_a_aux ;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Transcurrido: 00:00:11.46&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/pre&gt;&lt;/pre&gt;&lt;div style="text-align: justify;"&gt;El exchange se realizó en casi 12". Sumando la creación de la tabla auxiliar y el exchange, todo demoró solo 44"!!!, es decir mas de 6 veces mas rapido que el update tradicional.&lt;br /&gt;Ejecutando la consulta para obtener el espacio de redo y undo generado se obtiene:&lt;br /&gt;&lt;pre&gt;&lt;span style="font-weight: bold;"&gt;REDO_SIZE     1 Mb&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;UNDO_SIZE     0 Mb&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;Practicamente no hubo alocación de undo/redo. Por lo cual, para ciertos casos resulta muy util usar este metodo para actualizar dado que los tiempos de procesamiento se reducen sensiblemente y ademas los requerimientos de undo y redo son minimizados casi por completo.&lt;br /&gt;&lt;/div&gt;&lt;pre&gt;&lt;pre&gt;&lt;br /&gt;&lt;/pre&gt;&lt;/pre&gt;&lt;div style="text-align: justify;"&gt;Para eliminar (delete) en forma masiva, la creación de la tabla auxiliar solo deberá llenarse con las filas que no se borran. Si se necesitara borrar muchas filas de una tabla no particionada se podrá utilizar el mismo enfoque, es decir reemplazar el delete por un insert en una tabla nueva, recrear los indices y renombrar.&lt;br /&gt;&lt;/div&gt;&lt;pre&gt;&lt;pre&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5727738329548735676-2897447277631780178?l=oramdq.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://oramdq.blogspot.com/feeds/2897447277631780178/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://oramdq.blogspot.com/2010/11/como-realizar-updatedelete-masivos-en.html#comment-form' title='2 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/2897447277631780178'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/2897447277631780178'/><link rel='alternate' type='text/html' href='http://oramdq.blogspot.com/2010/11/como-realizar-updatedelete-masivos-en.html' title='Como realizar update/delete masivos en forma efectiva'/><author><name>Pablo A. Rovedo</name><uri>http://www.blogger.com/profile/02924687287216878757</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://3.bp.blogspot.com/_scsqOe7UOdc/SYDOV9ANLfI/AAAAAAAAAFQ/71M2XO1AZ1M/S220/DSC02067+(Small).JPG'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5727738329548735676.post-4443375475172352660</id><published>2010-11-17T10:42:00.000-08:00</published><updated>2010-11-18T11:23:00.029-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Scripts'/><title type='text'>Reportes de Métricas de Carga y Tiempos de Respuesta de la Base de Datos (10g+)</title><content type='html'>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.&lt;br /&gt;&lt;br /&gt;Con las nuevas vistas dinamicas se puede saber casi en tiempo real cual es la actividad de la base consultando las siguientes vistas dinamicas:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;V$SYSMETRIC&lt;/span&gt;         : Metricas mas recientes y menos recientes del ultimo minuto&lt;br /&gt;                 (una muestra cada 15").&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;V$SYSMETRIC_HISTORY&lt;/span&gt; : Ultima hora de todas las muestras (elige una muestra por&lt;br /&gt;                 minuto).&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;V$SYSMETRIC_SUMMARY&lt;/span&gt; : Resumen de la actividad de la ultima hora (maximos, minimos,&lt;br /&gt;                 promedios y desviación standard).&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;y las vistas historias que persisten parte de la información de las vistas dinamicas&lt;br /&gt;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:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;DBA_HIST_SYSMETRIC_HISTORY&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;DBA_HIST_SYSMETRIC_SUMMARY&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;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:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;select metric_name,&lt;br /&gt;  case (metric_id)&lt;br /&gt;  when 2016 then round(minval/1024/1024,2)&lt;br /&gt;  when 2058 then round(minval/1024/1024,2)&lt;br /&gt;  else round(minval,2) end Min,&lt;br /&gt;  case (metric_id)&lt;br /&gt;  when 2016 then round(maxval/1024/1024,2)&lt;br /&gt;  when 2058 then round(maxval/1024/1024,2)&lt;br /&gt;  else round(maxval,2) end Max,&lt;br /&gt;  case (metric_id)&lt;br /&gt;  when 2016 then round(average/1024/1024,2)&lt;br /&gt;  when 2058 then round(average/1024/1024,2)&lt;br /&gt;  else round(average,2) end Avg,&lt;br /&gt;  case (metric_id)&lt;br /&gt;  when 2016 then round(standard_deviation/1024/1024,2)&lt;br /&gt;  when 2058 then round(standard_deviation/1024/1024,2)&lt;br /&gt;  else round(standard_deviation,2) end STDDEV,&lt;br /&gt;  case (metric_id)&lt;br /&gt;  when 2016 then 'Mbytes Per Second'&lt;br /&gt;  when 2058 then 'Mbytes Per Second'&lt;br /&gt;  else metric_unit end metric_unit&lt;br /&gt;from v$sysmetric_summary&lt;br /&gt;where metric_id in (2003,2026.2004,2006,2016,2018,2030,&lt;br /&gt;               2044,2046,2058,2071,2075,2081,2123)&lt;br /&gt;order by metric_id&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;METRIC_NAME                                                             MIN        MAX        AVG     STDDEV METRIC_UNIT&lt;br /&gt;---------------------------------------------------------------- ---------- ---------- ---------- ---------- ----------------------------------------------------------------&lt;br /&gt;User Transaction Per Sec                                                  0      28.75       24.4       1.64 Transactions Per Second&lt;br /&gt;Physical Writes Per Sec                                                   0      29.63      21.91       2.25 Writes Per Second&lt;br /&gt;Redo Generated Per Sec                                                    0        .06        .05          0 Mbytes Per Second&lt;br /&gt;Logons Per Sec                                                            0       1.18        .93        .08 Logons Per Second&lt;br /&gt;Logical Reads Per Sec                                                     0    1015.72     592.05      75.19 Reads Per Second&lt;br /&gt;Total Parse Count Per Sec                                                 0      52.75      28.29       4.33 Parses Per Second&lt;br /&gt;Hard Parse Count Per Sec                                                  0       7.24       1.29        .88 Parses Per Second&lt;br /&gt;Network Traffic Volume Per Sec                                            0        .03        .02          0 Mbytes Per Second&lt;br /&gt;DB Block Changes Per Sec                                                  0     339.75     287.39      19.15 Blocks Per Second&lt;br /&gt;CPU Usage Per Sec                                                         0      11.27       9.88        .51 CentiSeconds Per Second&lt;br /&gt;User Rollback UndoRec Applied Per Sec                                     0         .3        .03        .07 Records Per Second&lt;br /&gt;Database Time Per Sec                                                     0      72.48      26.67        8.1 CentiSeconds Per Second&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Los datos anteriores se pueden obtener por transacción si se llegara a necesitar.&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;select metric_name,&lt;br /&gt;  round(value,2) value,&lt;br /&gt;  metric_unit&lt;br /&gt;from v$sysmetric&lt;br /&gt;where metric_name like '%\%%' escape '\'&lt;br /&gt;or metric_name like '%Percent%'&lt;br /&gt;or metric_name like '%Ratio%'&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;METRIC_NAME                                                           VALUE METRIC_UNIT&lt;br /&gt;---------------------------------------------------------------- ---------- ----------------------------------------------------------------&lt;br /&gt;Buffer Cache Hit Ratio                                                95.75 % (LogRead - PhyRead)/LogRead&lt;br /&gt;Memory Sorts Ratio                                                      100 % MemSort/(MemSort + DiskSort)&lt;br /&gt;Redo Allocation Hit Ratio                                               100 % (#Redo - RedoSpaceReq)/#Redo&lt;br /&gt;User Commits Percentage                                                 100 % (UserCommit/TotalUserTxn)&lt;br /&gt;User Rollbacks Percentage                                                 0 % (UserRollback/TotalUserTxn)&lt;br /&gt;Cursor Cache Hit Ratio                                               232.71 % CursorCacheHit/SoftParse&lt;br /&gt;Execute Without Parse Ratio                                           63.74 % (ExecWOParse/TotalExec)&lt;br /&gt;Soft Parse Ratio                                                      96.05 % SoftParses/TotalParses&lt;br /&gt;User Calls Ratio                                                      33.48 % UserCalls/AllCalls&lt;br /&gt;Host CPU Utilization (%)                                               4.63 % Busy/(Idle+Busy)&lt;br /&gt;PX downgraded 1 to 25% Per Sec                                            0 PX Operations Per Second&lt;br /&gt;PX downgraded 25 to 50% Per Sec                                           0 PX Operations Per Second&lt;br /&gt;PX downgraded 50 to 75% Per Sec                                           0 PX Operations Per Second&lt;br /&gt;PX downgraded 75 to 99% Per Sec                                           0 PX Operations Per Second&lt;br /&gt;User Limit %                                                              0 % Sessions/License_Limit&lt;br /&gt;Database Wait Time Ratio                                              42.12 % Wait/DB_Time&lt;br /&gt;Database CPU Time Ratio                                               57.88 % Cpu/DB_Time&lt;br /&gt;Row Cache Hit Ratio                                                   99.75 % Hits/Gets&lt;br /&gt;Row Cache Miss Ratio                                                    .25 % Misses/Gets&lt;br /&gt;Library Cache Hit Ratio                                                98.1 % Hits/Pins&lt;br /&gt;Library Cache Miss Ratio                                                1.9 % Misses/Gets&lt;br /&gt;Shared Pool Free %                                                    91.27 % Free/Total&lt;br /&gt;PGA Cache Hit %                                                       99.89 % Bytes/TotalBytes&lt;br /&gt;Process Limit %                                                        24.7 % Processes/Limit&lt;br /&gt;Session Limit %                                                       16.86 % Sessions/Limit&lt;br /&gt;Streams Pool Usage Percentage                                             0 % Memory allocated / Size of Streams pool&lt;br /&gt;Buffer Cache Hit Ratio                                                96.18 % (LogRead - PhyRead)/LogRead&lt;br /&gt;Memory Sorts Ratio                                                      100 % MemSort/(MemSort + DiskSort)&lt;br /&gt;Execute Without Parse Ratio                                           64.59 % (ExecWOParse/TotalExec)&lt;br /&gt;Soft Parse Ratio                                                      95.72 % SoftParses/TotalParses&lt;br /&gt;Host CPU Utilization (%)                                               4.39 % Busy/(Idle+Busy)&lt;br /&gt;Database CPU Time Ratio                                                15.8 % Cpu/DB_Time&lt;br /&gt;Library Cache Hit Ratio                                               97.64 % Hits/Pins&lt;br /&gt;Shared Pool Free %                                                    91.28 % Free/Total&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;select end_time,&lt;br /&gt;  round(max(decode(metric_id,2106,value/100,null)),4) "SQLRTime",&lt;br /&gt;  round(max(decode(metric_id,2109,value/100,null)),4) "RTime/Trx"&lt;br /&gt;from v$sysmetric_history&lt;br /&gt;where metric_id in (2106,2109)&lt;br /&gt;and end_time &gt; sysdate-10/24/60&lt;br /&gt;group by end_time&lt;br /&gt;order by end_time desc&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;END_TIME                   SQLRTime RTime/Trx&lt;br /&gt;18/11/2010 12:15:34 p.m.    0.0013    0.0172&lt;br /&gt;18/11/2010 12:14:33 p.m.    0.0063    0.0843&lt;br /&gt;18/11/2010 12:13:33 p.m.    0.0087    0.1101&lt;br /&gt;18/11/2010 12:12:33 p.m.    0.0039    0.1147&lt;br /&gt;18/11/2010 12:11:34 p.m.    0.009    0.1214&lt;br /&gt;18/11/2010 12:10:34 p.m.    0.0062    0.1145&lt;br /&gt;18/11/2010 12:09:34 p.m.    0.0079    0.1102&lt;br /&gt;18/11/2010 12:08:34 p.m.    0.0081    0.1167&lt;br /&gt;18/11/2010 12:07:34 p.m.    0.0085    0.1112&lt;br /&gt;18/11/2010 12:06:34 p.m.    0.0078    0.1141&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;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).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5727738329548735676-4443375475172352660?l=oramdq.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://oramdq.blogspot.com/feeds/4443375475172352660/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://oramdq.blogspot.com/2010/11/reportes-de-metricas-de-base-de-datos.html#comment-form' title='1 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/4443375475172352660'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/4443375475172352660'/><link rel='alternate' type='text/html' href='http://oramdq.blogspot.com/2010/11/reportes-de-metricas-de-base-de-datos.html' title='Reportes de Métricas de Carga y Tiempos de Respuesta de la Base de Datos (10g+)'/><author><name>Pablo A. Rovedo</name><uri>http://www.blogger.com/profile/02924687287216878757</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://3.bp.blogspot.com/_scsqOe7UOdc/SYDOV9ANLfI/AAAAAAAAAFQ/71M2XO1AZ1M/S220/DSC02067+(Small).JPG'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5727738329548735676.post-6122666673242403433</id><published>2010-11-17T04:35:00.000-08:00</published><updated>2010-11-17T05:03:45.791-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='11g R2'/><category scheme='http://www.blogger.com/atom/ns#' term='Upgrade'/><title type='text'>Aplicación de Parche 11.2.0.2 (no tan parche)</title><content type='html'>La semana pasada tuve que aplicar el parche 11.2.0.2 sobre un equipo de desarrollo. Cuando entré a metalink y busqué el parche que aplicaba a mi SO (Solaris SPARC) me llamó la atención el tamaño del parche. Los ultimos parches que instalé recuerdo que no pesaban mucho mas de 1Gb. El parche de 11.2.0.2 sobre la plataforma que necesitaba pesa 5.1Gb!, y para AIX mas de 6Gb. Recien una vez que leí la documentación entendí el porque. El tema es que Oracle cambió la politica de aplicación de parches a partir de 11.2.0.2. Ahora no son mas incrementales, sino totales y ademas contienen todo el bundle, es decir el server, cliente, gateway, grid, etc. &lt;br /&gt;&lt;br /&gt;Recuerdo que en 9i venia todo junto, si querias instalar solo un cliente necesitabas bajarte 3 archivos que contenian todo, lo cual resultaba engorroso. En 10g independizaron las instalaciones de cliente, server, grid, etc. Ahora parece que nuevamente hay que bajar todo y luego elegir en la instalación lo que necesitamos. &lt;br /&gt; Es obligatorio usar un home separado para la instalación, al ser total no se puede parchear sobre el home actual. En mi caso ese nuevo requisito no me molesta ya que siempre considero como una buena practica instalar los parches sobre una copia en un home nuevo, para minimizar riesgos por si el parche falla a la mitad de la instalación y la vuelta atrás requiere respaldar los binarios anteriores. Si estan muy cortos de espacio, se complica un poco crear un home separado asi que en ese caso habrá que bajar las bases, respaldar los binarios, borrarlos e instalar el nuevo home.&lt;br /&gt;&lt;br /&gt;La instalación sobre Solaris SPARC no tuvo contratiempos. Tuve que upgradear un Oracle 11g R1 (11.1.0.7) y un Oracle 11g R2 (11.2.0.1). Ambas actualizaciones se realizaron perfectamente y sin ningun contratiempo.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5727738329548735676-6122666673242403433?l=oramdq.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://oramdq.blogspot.com/feeds/6122666673242403433/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://oramdq.blogspot.com/2010/11/aplicacion-de-parche-11202-no-tan.html#comment-form' title='0 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/6122666673242403433'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/6122666673242403433'/><link rel='alternate' type='text/html' href='http://oramdq.blogspot.com/2010/11/aplicacion-de-parche-11202-no-tan.html' title='Aplicación de Parche 11.2.0.2 (no tan parche)'/><author><name>Pablo A. Rovedo</name><uri>http://www.blogger.com/profile/02924687287216878757</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://3.bp.blogspot.com/_scsqOe7UOdc/SYDOV9ANLfI/AAAAAAAAAFQ/71M2XO1AZ1M/S220/DSC02067+(Small).JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5727738329548735676.post-5230308452188030469</id><published>2010-10-08T11:37:00.001-07:00</published><updated>2010-10-26T05:02:43.593-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Procesamiento'/><category scheme='http://www.blogger.com/atom/ns#' term='Performance'/><title type='text'>Inserción Directa en Oracle (DIRECT INSERT)</title><content type='html'>La inserción directa de registros en tablas se realiza con las sentencias INSERT y MERGE (la parte de inserción) o desde una aplicación que utilice la interface directa de OCI (ej sqlloader). Cuando se necesita insertar un gran vólumen de filas en un tiempo óptimo, es necesario sacrificar cierta funcionalidad a expensas de velocidad. La mejora de rendimiento en la inserción no es gratis y hay ciertos requisitos que se deben cumplir y ciertas consecuencias a considerar antes de utilizarla. La inserción directa se activa de dos formas posibles:&lt;br /&gt;&lt;br /&gt;- Agregando el hint /*+ APPEND */ en la sentencia INSERT INTO... SELECT ..&lt;br /&gt;- Agregando el hint /*+ APPEND */ para INSERT INTO .. VALUES .. (en 11g R1)&lt;br /&gt;- Agregando el hint /*+ APPEND_VALUES */ para INSERT INTO .. VALUES .. (en 11g R2)&lt;br /&gt;- Ejecutando el insert en paralelo&lt;br /&gt;&lt;br /&gt;En los siguientes casos no se puede utilizar la inserción directa&lt;br /&gt;&lt;br /&gt;- La tabla a modificar tiene un trigger activo que se dispara con los inserts.&lt;br /&gt;- La tabla a modificar tiene una foreign key habilitada.&lt;br /&gt;- La tabla a modificar es una tabla indexada.&lt;br /&gt;- La tabla a modificar esta almacenada en un cluster.&lt;br /&gt;- La tabla a modificar contiene columna del tipo object type.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;A continuación voy a comparar el insert normal con el directo poniendo foco en el espacio de redo y undo consumido en cada caso. Tambien voy a mostrar como el modo directo "saltea" el buffer cache. Justamente esto ultimo es la clave para acelerar los inserts, ya que se arman los bloques nuevos en memoria y se agregan a la tabla en forma directa sin necesidad de usar el cache. Durante la inserciòn no se incrementa el HWM y solo se actualiza al commitear la transacción. Por este motivo no se puede realizar ninguna operación adicional sobre la tabla modificada hasta tanto no se haya confirmado la transacciòn de insert directo. Si intentamos ejecutar cualquier sentencia que referencie a tabla luego de insertar en modo directo Oracle genera el error: "ORA-12838: No se puede leer/modificar un objeto despues de modificarlo en paralelo". &lt;br /&gt;&lt;br /&gt;Para el ejemplo voy a crear una tabla T con dos columnas. Voy a usar una columna CHAR(500) para que se utilicen mas bloques sin tener que cargar tantas filas.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;drop table t&lt;br /&gt;&lt;br /&gt;create table t (x int,y char(500))&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Ahora se insertaran 100000 registros en forma normal:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;INSERT CONVENCIONAL&lt;br /&gt;-------------------&lt;br /&gt;&lt;br /&gt;insert into t &lt;br /&gt;select rownum,dbms_random.string('a',20)&lt;br /&gt;from dual&lt;br /&gt;connect by rownum &lt;= 100000&lt;br /&gt;&lt;br /&gt;El insert demoró: 10.8 segundos.&lt;br /&gt;&lt;br /&gt;select round(ms.value/1024/1024) redo_size&lt;br /&gt;from v$mystat ms,&lt;br /&gt;     v$statname sn&lt;br /&gt;where ms.STATISTIC# = sn.STATISTIC# &lt;br /&gt;  and sn.NAME = 'redo size'&lt;br /&gt;&lt;br /&gt;La cantidad de redo usado fue de: 53Mb&lt;br /&gt;&lt;br /&gt;  &lt;br /&gt;SELECT t.used_ublk*8 undo_size&lt;br /&gt;FROM v$transaction t, v$session s&lt;br /&gt;WHERE t.addr = s.taddr&lt;br /&gt;AND s.audsid = userenv('sessionid')  &lt;br /&gt;&lt;br /&gt;1104k&lt;br /&gt;&lt;br /&gt;La cantidad de undo usado fue de: 1104Kb&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Con la siguiente consulta chequeamos si se utilizo el buffer para el insert:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;select count(1)&lt;br /&gt;from v$bh bh,&lt;br /&gt;     user_objects ob&lt;br /&gt;where bh.OBJD = ob.data_object_id&lt;br /&gt;  and ob.object_name = 'T'&lt;br /&gt;  and bh.STATUS != 'free'&lt;br /&gt;  and bh.CLASS# = 1&lt;br /&gt;&lt;br /&gt;7174 bloques&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Se cargaron todos los bloques en cache para la inserción.&lt;br /&gt;&lt;br /&gt;Veamos que sucede con el insert en modo directo:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;INSERT DIRECTO&lt;br /&gt;--------------&lt;br /&gt;&lt;br /&gt;insert /*+ APPEND */ into t &lt;br /&gt;select rownum,dbms_random.string('a',20)&lt;br /&gt;from dual&lt;br /&gt;connect by rownum &lt;= 100000&lt;br /&gt;&lt;br /&gt;10.3s&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Demoró solo .5 segundos menos que el insert convencional. Esto puede llegar a desalentarnos de usar el modo directo, ya que no se ve tanta mejora, pero es importante aclarar que la diferencia de tiempos se va a notar mas cuando trabajemos con una cantidad de registros mas importante, yo diria del orden de los millones.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;select round(ms.value/1024/1024) redo_size&lt;br /&gt;from v$mystat ms,&lt;br /&gt;     v$statname sn&lt;br /&gt;where ms.STATISTIC# = sn.STATISTIC# &lt;br /&gt;  and sn.NAME = 'redo size'&lt;br /&gt;&lt;br /&gt;56Gb&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Ahora consumió un poco mas de redo, pero veamos el undo:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SELECT t.used_ublk*8 undo_size&lt;br /&gt;FROM v$transaction t, v$session s&lt;br /&gt;WHERE t.addr = s.taddr&lt;br /&gt;AND s.audsid = userenv('sessionid')  &lt;br /&gt;&lt;br /&gt;8kb&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Con el insert directo solo se consumieron 8k de undo, es decir 138 veces menos que con la el insert convencional.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;select count(1)&lt;br /&gt;from v$bh bh,&lt;br /&gt;     user_objects ob&lt;br /&gt;where bh.OBJD = ob.data_object_id&lt;br /&gt;  and ob.object_name = 'T'&lt;br /&gt;  and bh.STATUS != 'free'&lt;br /&gt;  and bh.CLASS# = 1&lt;br /&gt;  &lt;br /&gt;0 Bloques&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;No se usaron bloques de cache, justamente esto era esperable ya que como comenté mas arriba el insert directo no utiliza memoria intermedia.&lt;br /&gt;Como se vio con el insert directo se redujo notablemente el espacio de undo. Para reducir tambien el redo debemos realizar el insert sobre una tabla que tenga desactivado el logging, es decir que este en nologging (si la base esta en noarchivelog ya tiene desactivado el logging para la tablas cdo se inserta en modo directo).&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;INSERT DIRECTO CON NOLOGGING&lt;br /&gt;---------------------------------  &lt;br /&gt;&lt;br /&gt;insert /*+ APPEND */ into t &lt;br /&gt;select rownum,dbms_random.string('a',20)&lt;br /&gt;from dual&lt;br /&gt;connect by rownum &lt;= 100000&lt;br /&gt;&lt;br /&gt;9.6s&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Se redujo el tiempo de inserción, ahora fue de 9.6s&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;select round(ms.value/1024/1024) redo_size&lt;br /&gt;from v$mystat ms,&lt;br /&gt;     v$statname sn&lt;br /&gt;where ms.STATISTIC# = sn.STATISTIC# &lt;br /&gt;  and sn.NAME = 'redo size'&lt;br /&gt;&lt;br /&gt;0&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;El consumo de redo fue nulo. Si bien esto parece muy bueno hay que tener en cuenta que deshabilitar el logging (en realidad se minimiza, ya que operaciones internas como correr el HWM o agregar extent generan redo y undo) provoca que ante un evento de falla no podamos realizar recovery de los datos recien insertados. Es recomendable realizar un backup lógico de las tablas o un backup incremental con RMAN inmediatamente luego de la carga directa. No es posible "bypassear" el redo para las tablas alojadas en tablespaces con force logging. &lt;br /&gt;&lt;pre&gt;&lt;br /&gt;select round(ms.value/1024/1024) redo_size&lt;br /&gt;from v$mystat ms,&lt;br /&gt;     v$statname sn&lt;br /&gt;where ms.STATISTIC# = sn.STATISTIC# &lt;br /&gt;  and sn.NAME = 'redo size'&lt;br /&gt;&lt;br /&gt;8 kb&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;El undo se mantuvo exactamente igual que la prueba anterior.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;select round(ms.value/1024/1024) redo_size&lt;br /&gt;from v$mystat ms,&lt;br /&gt;     v$statname sn&lt;br /&gt;where ms.STATISTIC# = sn.STATISTIC# &lt;br /&gt;  and sn.NAME = 'redo size'&lt;br /&gt;&lt;br /&gt;0 bloques&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;y tampoco se cargaron bloques en memoria.&lt;br /&gt;&lt;br /&gt;Recordemos que para las 3 pruebas anteriores utilizamos una tabla sin indices. Veamos que pasa cuando se le agrega un indice a la tabla T&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;create index t_idx on t (x)&lt;br /&gt;&lt;br /&gt;insert /*+ APPEND */ into t &lt;br /&gt;select rownum,dbms_random.string('a',20)&lt;br /&gt;from dual&lt;br /&gt;connect by rownum &lt;= 100000&lt;br /&gt;&lt;br /&gt;11.2.s&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Claramente el insert demoró mas, ya que tuvo que mantener el indice actualizado durante las inserciones.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;select round(ms.value/1024/1024) redo_size&lt;br /&gt;from v$mystat ms,&lt;br /&gt;     v$statname sn&lt;br /&gt;where ms.STATISTIC# = sn.STATISTIC# &lt;br /&gt;  and sn.NAME = 'redo size'&lt;br /&gt;&lt;br /&gt;7&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;El espacio consumido de redo ahora no fue 0, sigue siendo poco pero ahora es de 7kb, ya que tuvo que guardar información de redo para el indice.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SELECT t.used_ublk*8 undo_size&lt;br /&gt;FROM v$transaction t, v$session s&lt;br /&gt;WHERE t.addr = s.taddr&lt;br /&gt;AND s.audsid = userenv('sessionid')  &lt;br /&gt;&lt;br /&gt;1600 kb&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;El consumo de undo tambien se incremento dado que se guardaron registros de undo para el nuevo indice&lt;br /&gt;&lt;br /&gt;La siguiente tablita resume el resultado de las pruebas:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_scsqOe7UOdc/TMHmVke8pdI/AAAAAAAAAL4/XFSIj39HRhk/s1600/Inserts.bmp"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 400px; height: 69px;" src="http://2.bp.blogspot.com/_scsqOe7UOdc/TMHmVke8pdI/AAAAAAAAAL4/XFSIj39HRhk/s400/Inserts.bmp" border="0" alt=""id="BLOGGER_PHOTO_ID_5530955075632801234" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Como resumen, recomiendo utilizar siempre que sea posible el modo directo sobre todo cuando se necesita realizar una carga masiva (procesos ETL) y la performance en la carga es el principal objetivo. Tambien, en lo posible, y teniendo conciencia de lo que implica usar nologging, tambien recomiendo configurar la tabla receptoras en nologging. Por ultimo, es recomendable deshabilitar los indices (ponerlos en unusables) ya que como vimos en el ultimo caso, el mantenimiento de los indices durante la inserción genera mas undo/redo y ralentiza la carga general. Luego de la inserción habrá que reconstruir los indices inutilizados.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5727738329548735676-5230308452188030469?l=oramdq.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://oramdq.blogspot.com/feeds/5230308452188030469/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://oramdq.blogspot.com/2010/10/insercion-directa-en-oracle-direct.html#comment-form' title='0 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/5230308452188030469'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/5230308452188030469'/><link rel='alternate' type='text/html' href='http://oramdq.blogspot.com/2010/10/insercion-directa-en-oracle-direct.html' title='Inserción Directa en Oracle (DIRECT INSERT)'/><author><name>Pablo A. Rovedo</name><uri>http://www.blogger.com/profile/02924687287216878757</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://3.bp.blogspot.com/_scsqOe7UOdc/SYDOV9ANLfI/AAAAAAAAAFQ/71M2XO1AZ1M/S220/DSC02067+(Small).JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_scsqOe7UOdc/TMHmVke8pdI/AAAAAAAAAL4/XFSIj39HRhk/s72-c/Inserts.bmp' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5727738329548735676.post-871565438853592786</id><published>2010-09-22T11:29:00.000-07:00</published><updated>2011-01-25T05:53:37.567-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Capacity Planning'/><title type='text'>Como realizar una estimación de capacidad y escalabilidad de CPU y I/O (Capacity Planning)</title><content type='html'>En esta nota les voy a mostrar como realizar un Capacity Planning (CP) de CPU y I/O en forma sencilla usando matemática simple. Para hacer un CP efectivo es importante determinar los subsistemas a modelar. Los subsistemas tipicos son CPU, I/O o discos, memoria y red. Existen productos comerciales que permiten realizar un CP global, sin tener que modelar por subsistema, pero yo prefiero analizar cada subsistema por separado ya que me parece que los resultados obtenidos serán mas precisos y claros. La CPU y I/O son claramente los mas importantes y son los subsistemas en los que vamos a ahondar en esta nota.&lt;br /&gt;&lt;br /&gt;Seguramente alguna vez su gerente les ha consultado si la cantidad de procesadores o la velocidad de los discos es adecuada para garantizar que el sistema permanezca estable. Ante esta pregunta uno puede simplemente tener confianza y contestar que todo va a funcionar bien o uno puede ser mas profesional y realizar un CP y saber con exactitud  en donde estará el punto de quiebre, es decir no solo contestar si el HW soporta la carga actual sino tambien, teniendo la proyección de crecimiento transacccional anual, saber hasta donde nos alcanza nuestro HW y preveer con tiempo la compra o upgrade de HW.&lt;br /&gt;&lt;br /&gt;Ahora vayamos directamente a ver como se modela un subsistema de cpu y un subsistema de i/o. Para realizar CP mas sofisticado es necesario tener cierto conocimiento de Teoria de Colas (función Erlang C, Notación de Kendall, etc). En esta nota simplemente alcanza con un poco de criterio. En el gráfico A) se modela el subsistema de CPU. Este consta de una sola cola y N servers (cpu's). Esto quiere decir que cada petición puede ser atendida arbitrariamente por cualquier server o cpu y se van encolando cuando todas las cpu estan ocupadas (runqueue en terminos de SO). En el gráfico B) se modela el subsistema de I/O donde tenemos una cola por server o device. En este caso cada petición debe ser atendida por un device determinado.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_scsqOe7UOdc/TJpYNEiB-JI/AAAAAAAAAKw/-60xgKsW4Kw/s1600/CPU_IO_CP.bmp"&gt;&lt;img style="cursor: pointer; width: 400px; height: 141px;" src="http://2.bp.blogspot.com/_scsqOe7UOdc/TJpYNEiB-JI/AAAAAAAAAKw/-60xgKsW4Kw/s400/CPU_IO_CP.bmp" alt="" id="BLOGGER_PHOTO_ID_5519821274873985170" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Los dos modelos mencionados se representan infinidad de veces en la vida cotidiana, cuando vamos al banco, cuando estamos esperando en la estación de peaje, cuando esperamos para abordar un vuelo, cuando vamos a hacer nuestro pedido en un restaurant de comida rapida, etc. &lt;br /&gt;&lt;br /&gt;Ahora vamos a definir las variables que necesitamos usar para realizar el CP:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Uc= User Calls&lt;br /&gt;trx= transacción&lt;br /&gt;λ= tasa de arribo  (ej: trx/ms ó Uc/ms)&lt;br /&gt;St= Tiempo de Servicio  (ej: s/trx ó s/Uc)&lt;br /&gt;Qt= Tiempo de Espera o Tiempo de espera  (ej: s/trx)&lt;br /&gt;Rt= Tiempo de Respuesta  (ej: s/trx)&lt;br /&gt;Q= Encolamiento   (ej: Nro de trx's)&lt;br /&gt;U= porcentaje de carga (porcentaje de utlización del recurso)&lt;br /&gt;M= nro de servers   (cantidad de CPU's o cantidad de IO devices)&lt;br /&gt;&lt;br /&gt;y las formulas:&lt;br /&gt;&lt;br /&gt;Rt = St + Qt&lt;br /&gt;&lt;br /&gt;U = St * λ / M&lt;br /&gt;&lt;br /&gt;Q = λ * Qt&lt;br /&gt;&lt;br /&gt;Rt(cpu) = St / (1-U^M)&lt;br /&gt;&lt;br /&gt;Rt(io)  = St / (1-U)&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;El objetivo es conseguir graficar el tiempo de respuesta vs tasa de arribo. El gráfico debería ser tipo exponencial con una curvatura o punto de quiebre que muestre a que tasa de arribo (eje X) se produce dicho quiebre.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_scsqOe7UOdc/TJyakLVCmoI/AAAAAAAAAK4/gFSl5aC3Wgk/s1600/arrival_vs_rt.jpg"&gt;&lt;img style="cursor: pointer; width: 274px; height: 184px;" src="http://1.bp.blogspot.com/_scsqOe7UOdc/TJyakLVCmoI/AAAAAAAAAK4/gFSl5aC3Wgk/s400/arrival_vs_rt.jpg" alt="" id="BLOGGER_PHOTO_ID_5520457189556394626" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Voy a explicar un poco mas en detalle que significa cada cosa:&lt;br /&gt;&lt;br /&gt;1) λ (Tasa de Arribo):  Esta se puede definir con métricas de Oracle tales como:  Uc/s o Trs/s,  o usar una métrica funcional, por ejemplo: carga de ordenes de compra por minuto o por hora.&lt;br /&gt;&lt;br /&gt;2) M (Cantidad de CPU´s y I/O Devices):  Es la cantidad de cpu's o I/O Devices. Para obtener este valor podemos usar utilitarios de SO y/o consultar vistas de Oracle.&lt;br /&gt;&lt;br /&gt;3) U (Porcentaje de Carga): Es el porcentaje de carga de cpu o I/O. Esto se puede obtener usando utilitarios de SO como sar, iostat.&lt;br /&gt;&lt;br /&gt;4) St (Tiempo de servicio): Es el tiempo en el que la petición esta siendo atendida por la cpu o por el dispositivo de i/o.&lt;br /&gt;&lt;br /&gt;5) Qt (Tiempo de Espera en la Cola): Es el tiempo que una petición tiene que esperar en la cola porque los servers estan ocupados.&lt;br /&gt;&lt;br /&gt;6) Q  (Largo de la Cola): Es la cantidad de peticiones encoladas (esperando ser atendidas)&lt;br /&gt;&lt;br /&gt;7) Rt (Tiempo de respuesta): El tiempo de respuesta es la suma de St (service time) y Qt (tiempo de espera o encolado). Una petición o esta esperando o esta siendo atendido.&lt;br /&gt;&lt;br /&gt;En este punto, donde creo haber definido todo, les voy a mostrar un ejemplo:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Ejemplo (Capacity Planning de CPU)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Supongamos  nos piden estimar el impacto que se producirá si se incrementan los usuarios de una aplicación X en un 20%.  Lo que se quiere determinar es si se necesitará agregar mas procesadores o con lo que hay es suficiente para soportar la carga adicional.&lt;br /&gt;&lt;br /&gt;Lo primero que tenemos que hacer es recolectar información en un periodo representativo. Cuanto mas datos recolectemos mas precisa será nuestro CP, luego debemos caracterizar la carga, esto significa definir si vamos a usar valores promedio, maximos, que vamos a usar como tasa de arribo, etc. En el ejemplo yo voy a tomar valores picos en un intervalo de maxima carga y como tasa de arribo usaré Trx/s.&lt;br /&gt;&lt;br /&gt;λ= 20 trx/s  (20 transacciones por segundo, se puede obtener desde statspack o AWR)&lt;br /&gt;U= 0,40 (40% de utilización de cpu,  esto se puede recolectar con sar -u)&lt;br /&gt;M= 8 (hay 8 procedores, se obtiene desde el parametro de la base: cpu_count)&lt;br /&gt;&lt;br /&gt;Ya tengo los 3 parametros escenciales, ahora solo tengo que aplicar las formulas y graficar:&lt;br /&gt;&lt;span style="font-family:monospace;"&gt;&lt;br /&gt;&lt;/span&gt;U = St * λ / M, depejando para obtener St,&lt;br /&gt;&lt;br /&gt; St = U * M / λ = 0.40 * 8 / 20 =  0,16 s/trx&lt;br /&gt;&lt;br /&gt; Ahora que tenemos St podemos aplicar la formula para cpu:&lt;br /&gt;&lt;span style="font-family:monospace;"&gt;&lt;/span&gt;&lt;br /&gt; Rt(cpu) = St / (1-U^M)  =    0,16 / (1-0.40^8)  =  0,16 s/trx&lt;br /&gt;&lt;br /&gt; Como se observa el tiempo de respuesta es igual al tiempo de servicio. Esto se da porque el sistema esta holgado en cpu para la carga actual y no se producen encolamientos, es decir hay que esperar para ser atendido por las cpu's. Ahora lo que voy a hacer es una tabla en excel para proyectar el crecimiento y graficar la curva de manera de ver donde se produce el quiebre.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_scsqOe7UOdc/TT7VKVILzKI/AAAAAAAAAME/3mmP-rciJpQ/s1600/cpu_cp.bmp"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 400px; height: 361px;" src="http://1.bp.blogspot.com/_scsqOe7UOdc/TT7VKVILzKI/AAAAAAAAAME/3mmP-rciJpQ/s400/cpu_cp.bmp" border="0" alt=""id="BLOGGER_PHOTO_ID_5566120562923392162" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Vemos que el punto de quiebre esta alrededor del 30-35% (&gt;), pensemos además que estamos partiendo de un workload pico y por lo tanto podemos pensar que la mayoria del tiempo va a estar bastante por debajo. De acuerdo al CP podemos asegurar que el sistema amortiguará bien la carga adicional del 20%, comenzando a degradarse rapidamente a partir del 25%.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Ejemplo 2 (Capacity Planning de I/O)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Como analizamos en el ejemplo 1, no tendriamos mayores problemas con la cpu al incrementar en un 30% la carga. Ahora veamos que ocurre con los discos.&lt;br /&gt;&lt;br /&gt;λ= 5Mb/ms  (5 Mb de transferencia por milisegundo, se puede obtener con sar -d o iostat)&lt;br /&gt;U= 0,60 (60% de utilización de i/o)&lt;br /&gt;M= 50 (hay 50 devices)&lt;br /&gt;&lt;br /&gt;St = U * M / λ = 50 * 0.60 / 5 =  6 ms/Mb&lt;br /&gt;&lt;br /&gt;Reemplazando en la formula de i/o:&lt;br /&gt;&lt;br /&gt;Rt(io)  = St / (1-U) =  6 / (1-0.60) =  15ms/Mb&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_scsqOe7UOdc/TJzno0e0-nI/AAAAAAAAALo/CVG51p8gQ24/s1600/CP_io.bmp"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 351px; height: 400px;" src="http://2.bp.blogspot.com/_scsqOe7UOdc/TJzno0e0-nI/AAAAAAAAALo/CVG51p8gQ24/s400/CP_io.bmp" border="0" alt=""id="BLOGGER_PHOTO_ID_5520541931718113906" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Como se ve en el gráfico, el sistema podría soportar hasta un 50% (7.5Mb/ms) de crecimiento, una vez que llega al 60% de destabiliza. &lt;br /&gt;&lt;br /&gt;Como resumen podemos inferir que nuestro Hardware esta bien a nivel cpu no tanto a nivel i/o ya que no escalará a largo plazo. &lt;br /&gt;&lt;br /&gt;En esta nota mi idea fue mostrar como realizar un capacity planning de cpu y i/o en forma sencilla. Con este metodo tendremos una buena estimación de como escalará nuestro HW. De acuerdo a cada caso existen otros enfoque para modelar tales como el metodo basado en ratios, Teoria de Colas y metodo de regresión lineal. &lt;br /&gt;&lt;br /&gt;Lo mas importante para obtener estimaciones precisas es obtener una buena muestra, que sea representativa sumado a una buena caracterización de la carga de trabajo a evaluar. Cuanto mas información hayamos recolectado mejor será nuestro pronóstico.&lt;br /&gt;&lt;br /&gt;Usando esta metodologia se puede realizar simulaciones y proyectar a futuro, por ejemplo: que pasaría si agregamos o sacamos cpu's, como impacta el agregado de dispositivos de i/o mas rapidos, con mayor throughput y menor latencia, cuantos usuarios se podrian agregar para que operen con la aplicación sin poner en riesgo la estabilidad del sistema, etc.&lt;br /&gt;&lt;br /&gt;Para los que le interesen temas de Capacity Planning en Oracle les recomiendo:  &lt;br /&gt;&lt;span style="color: rgb(255, 0, 0); font-weight: bold;"&gt; &lt;/span&gt;&lt;a style="color: rgb(255, 0, 0); font-weight: bold;" href="http://www.amazon.com/Forecasting-Oracle-Performance-Craig-Shallahamer/dp/1590598024" rel="stylesheet"&gt;"Forecasting Oracle Performance"&lt;/a&gt;. Este libro escrito por Craig Shallahamer, un verdadero gurú en el tema, me pareció excelente y lo usé como referencia para escribir la nota.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5727738329548735676-871565438853592786?l=oramdq.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://oramdq.blogspot.com/feeds/871565438853592786/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://oramdq.blogspot.com/2010/09/una-forma-sencilla-de-realizar.html#comment-form' title='0 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/871565438853592786'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/871565438853592786'/><link rel='alternate' type='text/html' href='http://oramdq.blogspot.com/2010/09/una-forma-sencilla-de-realizar.html' title='Como realizar una estimación de capacidad y escalabilidad de CPU y I/O (Capacity Planning)'/><author><name>Pablo A. Rovedo</name><uri>http://www.blogger.com/profile/02924687287216878757</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://3.bp.blogspot.com/_scsqOe7UOdc/SYDOV9ANLfI/AAAAAAAAAFQ/71M2XO1AZ1M/S220/DSC02067+(Small).JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_scsqOe7UOdc/TJpYNEiB-JI/AAAAAAAAAKw/-60xgKsW4Kw/s72-c/CPU_IO_CP.bmp' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5727738329548735676.post-2323903723389480540</id><published>2010-09-20T12:39:00.000-07:00</published><updated>2010-09-21T11:51:14.063-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='11g New Features'/><category scheme='http://www.blogger.com/atom/ns#' term='DataWarehousing'/><category scheme='http://www.blogger.com/atom/ns#' term='Tuning'/><category scheme='http://www.blogger.com/atom/ns#' term='Performance'/><title type='text'>Optimizando la recolección estadística sobre tablas particionadas</title><content type='html'>Oracle 10g utiliza un algoritmo de dos pasadas para recolectar estadisticas sobre tablas particionadas:&lt;br /&gt;&lt;br /&gt;1. Una pasada por sobre toda la tabla para actualizar las estadisticas globales.&lt;br /&gt;2. Una segunda pasada para recolectar estadisticas en cada una de las particiones.&lt;br /&gt;&lt;br /&gt;Este enfoque tiene el inconveniente que si se realizan cambios en algunas pocas particiones que las hagan elegibles para la recolección automática en la ventana de mantenimiento, además de refrescar la información estadistica propia de las particiones en cuestión, se deberá realizar la actualización global de la tabla. Para esto ultimo se recorre toda la tabla, incluso las particiones que no tuvieron cambio alguno. Esto puede realizar muy pesado dependiendo del tamaño de la tabla.&lt;br /&gt;&lt;br /&gt;A partir de Oracle 11g se adopta una algoritmo de una  sola pasada, de manera de que en lugar de realizar una pasada por toda la tabla para actualizar la información global, se realiza una actualización incremental infiriendo los cambios desde las particiones modificadas. Algunas de las estadisticas pueden ser derivadas facilmente desde las estadisticas de las particiones (por ejemplo el número de filas), pero otras estadisticas, tal como el número de valores distintos de una columna no. Para resolver esto Oracle usa una nueva estructura llamada sinopsis para cada columna a nivel de la partición de forma tal que el numero de valores distintos (NDV) a nivel global pueda ser derivado haciendo merge de las sinopsis de las particiones analizadas.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_scsqOe7UOdc/TJitwk-NFzI/AAAAAAAAAKo/ELlyt1WTRHU/s1600/incremental_stats_gathering.jpg"&gt;&lt;img style="cursor: pointer; width: 400px; height: 298px;" src="http://2.bp.blogspot.com/_scsqOe7UOdc/TJitwk-NFzI/AAAAAAAAAKo/ELlyt1WTRHU/s400/incremental_stats_gathering.jpg" alt="" id="BLOGGER_PHOTO_ID_5519352393412450098" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Si bien esto es un feature de 11g R1, en Oracle 10g R2, mas precisamente en 10.2.0.4 existe una opción para simular la recolección incremental mediante un nuevo valor 'APPROX_GLOBAL AND PARTITION' para el parametro GRANULARITY en el procedimiento GATHER_TABLE_STATS. Su comportamiento es igual al de 11g excepto para&lt;br /&gt;los NDV de las columna no particionadas y para el número de claves distintas del indice a nivel global.&lt;br /&gt;&lt;br /&gt;El mantenimiento incremental esta deshabilitado por default y se puede habilitar a nivel tabla, esquema, incluso a nivel de la base de datos.&lt;br /&gt;&lt;br /&gt;A continuación les paso el resultado de mis pruebas usando Oracle 11g R1 (11.1.0.7):&lt;br /&gt;&lt;br /&gt;Voy a usar una tabla particionada por rango de fechas con 3 particiones. La tabla es pequeña (alrededor de 5M de filas) pero servirá para ejemplificar:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;select partition_name,num_rows&lt;br /&gt;from user_tab_partitions where table_name = 'T';&lt;br /&gt;&lt;br /&gt;PARTITION_NAME                   NUM_ROWS&lt;br /&gt;------------------------------ ----------&lt;br /&gt;P0710                             1332466&lt;br /&gt;P0810                             2583379&lt;br /&gt;P0910                             1084155&lt;br /&gt;PMAX                                    0&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Voy a eliminar 100,000 registros de una de las particiones:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;delete from t partition (p0910)&lt;br /&gt;where rownum &lt;= 100000;    100000 filas suprimidas.  &lt;/pre&gt;&lt;br /&gt;Actualizo las estadisticas, usando el default, es decir sin recolección incremental:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;begin&lt;br /&gt;dbms_stats.gather_table_stats(ownname =&gt; user,tabname =&gt; 'T');&lt;br /&gt;end;&lt;br /&gt;&lt;br /&gt;Procedimiento PL/SQL terminado correctamente.&lt;br /&gt;Transcurrido: &lt;span style="font-weight: bold;"&gt;00:00:11.71&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Demoró casi 12 segundos.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;select dbms_stats.get_prefs('INCREMENTAL', tabname=&gt;'T') from dual;&lt;br /&gt;&lt;br /&gt;FALSE&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Con la consulta de arriba verificamos que se hizo la recolección convencional&lt;br /&gt;&lt;br /&gt;Ahora voy a activar la recolección sobre la tabla T y voy eliminar filas y voy a volver a recolectar las estadisticas:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;begin&lt;br /&gt;dbms_stats.set_table_prefs(ownname =&gt; user,tabname =&gt; 'T',&lt;br /&gt;                           pname =&gt; 'INCREMENTAL',pvalue =&gt; 'TRUE');&lt;br /&gt;end;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Verificamos que efectivamente esta activado el modo incremental sobre la tabla T:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;select dbms_stats.get_prefs('INCREMENTAL', tabname=&gt;'T') from dual;&lt;br /&gt;&lt;br /&gt;TRUE&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;begin  &lt;br /&gt;dbms_stats.gather_table_stats(ownname =&gt; user,tabname =&gt; 'T');&lt;br /&gt;end;&lt;br /&gt;&lt;br /&gt;Transcurrido: &lt;span style="font-weight: bold;"&gt;00:00:04.71&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Ahora demoró 4s. En lugar de recorrer toda la tabla solo analizó la partición que cambió y luego derivó las estadisticas globales en base a los cambios efectuados y usando la sinapsis de la partición.&lt;br /&gt;&lt;br /&gt;Hay que tomar en cuenta que los histogramas globales no se preservan luego de ejecutar la recolección incremental (ver Bug 8686932 en Metalink).&lt;br /&gt;&lt;br /&gt;Si bien este método usado por Oracle para hacer mas efectiva la recolección se viene estudiando en ambitos academicos y de laboratorio hace ya tiempo, es Oracle el primer motor de base relacional en implementarlo.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5727738329548735676-2323903723389480540?l=oramdq.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://oramdq.blogspot.com/feeds/2323903723389480540/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://oramdq.blogspot.com/2010/09/optimizando-la-recoleccion-estadistica.html#comment-form' title='0 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/2323903723389480540'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/2323903723389480540'/><link rel='alternate' type='text/html' href='http://oramdq.blogspot.com/2010/09/optimizando-la-recoleccion-estadistica.html' title='Optimizando la recolección estadística sobre tablas particionadas'/><author><name>Pablo A. Rovedo</name><uri>http://www.blogger.com/profile/02924687287216878757</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://3.bp.blogspot.com/_scsqOe7UOdc/SYDOV9ANLfI/AAAAAAAAAFQ/71M2XO1AZ1M/S220/DSC02067+(Small).JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_scsqOe7UOdc/TJitwk-NFzI/AAAAAAAAAKo/ELlyt1WTRHU/s72-c/incremental_stats_gathering.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5727738329548735676.post-3531639569241360852</id><published>2010-08-24T12:13:00.000-07:00</published><updated>2010-08-24T12:56:35.199-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Misceláneo'/><title type='text'>Como obtener la edición del sofware instalado desde el inventory</title><content type='html'>Para obtener la edición (Enterprise o Standard) del software Oracle (motor) instalado ,si todavia no se creo una base de datos (se puede sacar la edición desde el catalogo de la base) y si la persona que lo instaló no recuerda que opción eligió, se puede ejecutar el siguiente comando desde Unix/Linux que busca en el inventory:&lt;br /&gt;&lt;br /&gt;$grep -w s_serverInstallType $ORACLE_HOME/inventory/Components21/oracle.server/*/context.xml |&lt;br /&gt;  tr ' ' '\n' |&lt;br /&gt;  grep VAL&lt;br /&gt;&lt;br /&gt;VAL="EE"&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;En el ejemplo mostró "EE" = Enterprise Edition, si el software fuera Standard Edition retornará "SE".&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5727738329548735676-3531639569241360852?l=oramdq.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://oramdq.blogspot.com/feeds/3531639569241360852/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://oramdq.blogspot.com/2010/08/como-obtener-la-edicion-del-sofware.html#comment-form' title='0 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/3531639569241360852'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/3531639569241360852'/><link rel='alternate' type='text/html' href='http://oramdq.blogspot.com/2010/08/como-obtener-la-edicion-del-sofware.html' title='Como obtener la edición del sofware instalado desde el inventory'/><author><name>Pablo A. Rovedo</name><uri>http://www.blogger.com/profile/02924687287216878757</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://3.bp.blogspot.com/_scsqOe7UOdc/SYDOV9ANLfI/AAAAAAAAAFQ/71M2XO1AZ1M/S220/DSC02067+(Small).JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5727738329548735676.post-4842239154947375875</id><published>2010-08-12T12:21:00.000-07:00</published><updated>2010-08-13T07:12:11.382-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='11g New Features'/><category scheme='http://www.blogger.com/atom/ns#' term='Upgrade'/><category scheme='http://www.blogger.com/atom/ns#' term='Tuning'/><title type='text'>Benchmarking de performance de sentencias usando SQL Performance Analyzer  (SPA)</title><content type='html'>En esta nota voy a mostrarles como usar SQL Performance Analyzer (SPA) que es parte del Suite Real Application Testing, y sirve para evaluar y predecir de que forma se afectaran los planes de las sentencias luego de cambios en el entorno tales como:&lt;br /&gt;&lt;br /&gt;* Upgrade de BD, HW o SO.&lt;br /&gt;* Cambios en la configuración de BD, HW o SO.&lt;br /&gt;* Cambio de parametrizacion de BD&lt;br /&gt;* Cambios en el esquema de datos (agregado de indices, vistas materializadas)&lt;br /&gt;* Estado de las estadisticas de objetos y de sistema.&lt;br /&gt;&lt;br /&gt;Este nuevo feature es muy util para analizar el impacto de cambio ya que permite "jugar" facilmente con el entorno y realizar reportes comparativos, pruebas de regresión, impacto de carga, etc.&lt;br /&gt;&lt;br /&gt;Ahora voy a armar un escenario de prueba en 11g para comparar un simple count de la tabla T usando el optimizador por reglas (RBO) contra el mismo count usando el optimizador CBO. Es claro, que RBO esta desoportado desde 10g y que el ejemplo no representa un caso real, o tal vez si, pero va a servir para mostrar como funciona SPA y de paso mostrarles que tan "ciego" es RBO en ciertos casos que intuitivamente parecen triviales.&lt;br /&gt;&lt;br /&gt;Voy a crear un tabla T con 1M de registros y con pctfree del 90% para consumir muchos bloques tal que la diferencia entre las ejecuciones que voy a comparar sea mas notoria. Luego voy a crear una PK por el campo id. Con RBO va a hacer un FULL SCAN sobre la tabla T, ya que no se da cuenta que tiene una PK y que podría hacer un full index scan que es mas rapido. Obviamente CBO se percata de esto ya que al ser justamente PK tiene la misma cantidad de registros que la tabla y por lo tanto sirve para responder a la pregunta de cuantos registros tiene la tabla T.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;create table t (id int,val varchar2(10)) pctfree 90;&lt;br /&gt;&lt;br /&gt;insert into t &lt;br /&gt;as select rownum ,dbms_random.string('a',10)&lt;br /&gt;from dual&lt;br /&gt;connect by rownum &lt;= 1000000 ;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;alter table t add primary key (id);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Ejecuto la sentencia de prueba, uso un hint para ubicarla mas facilmente en la vista dinamica y obtener su sqlid:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;select count(1) /*+ Prueba SPA */ from t;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;select sql_id from v$sql &lt;br /&gt;where sql_text like '%Prueba SPA%' and sql_text not like '%sql_text%'; &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;5r2ufj2vqkk4p&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Ya tengo el sqlid, asi que lo que voy a hacer es armar un SQL Tuning Set (STS) usando el paquete DBMS_SQLTUNE (esto existe desde 10g, asi que lo podria hacer en 10g y luego migrarlo a 11g, por ejemplo para evaluar un upgrade entre esas versiones)&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;begin&lt;br /&gt;    dbms_sqltune.create_sqlset(sqlset_name =&gt; 'Prueba',description =&gt; 'STS de Prueba');&lt;br /&gt;end;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;El STS que cree se llaman Prueba, ahora le cargo la sentencia desde su cursor en memoria:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;DECLARE&lt;br /&gt;    l_cursor  DBMS_SQLTUNE.sqlset_cursor;&lt;br /&gt;BEGIN&lt;br /&gt;      OPEN l_cursor FOR&lt;br /&gt;      SELECT VALUE(p)&lt;br /&gt;      FROM   TABLE (&lt;br /&gt;             DBMS_SQLTUNE.select_cursor_cache (&lt;br /&gt;                 'sql_id = ''5r2ufj2vqkk4p''', -- basic_filter&lt;br /&gt;                 NULL, -- object_filter&lt;br /&gt;                 NULL, -- ranking_measure1&lt;br /&gt;                 NULL, -- ranking_measure2&lt;br /&gt;                 NULL, -- ranking_measure3&lt;br /&gt;                 NULL, -- result_percentage&lt;br /&gt;                 1)    -- result_limit&lt;br /&gt;           ) p;&lt;br /&gt;     &lt;br /&gt;      DBMS_SQLTUNE.load_sqlset (&lt;br /&gt;           sqlset_name    =&gt; 'Prueba',&lt;br /&gt;           populate_cursor =&gt; l_cursor);&lt;br /&gt;END;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Verificamos que efectivamente se haya creado el STS y que contenga la sentencia:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;select * from user_sqlset where name = 'Prueba';&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;NAME                                   ID&lt;br /&gt;------------------------------ ----------&lt;br /&gt;DESCRIPTION&lt;br /&gt;------------------------------------------------------------------------------------&lt;br /&gt;CREATED   LAST_MODI STATEMENT_COUNT&lt;br /&gt;--------- --------- ---------------&lt;br /&gt;Prueba                                 10&lt;br /&gt;STS de Prueba&lt;br /&gt;12-AGO-10 12-AGO-10               1&lt;br /&gt;&lt;br /&gt;select sqlset_name,sql_id from user_sqlset_statements where sql_id = '5r2ufj2vqkk4p';&lt;br /&gt;&lt;br /&gt;SQLSET_NAME                    SQL_ID&lt;br /&gt;------------------------------ -------------&lt;br /&gt;Prueba                         5r2ufj2vqkk4p&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;En este punto, y teniendo creado el STS, que contiene la sentencia mas la información de contexto para evaluarla, podemos comenzar a utilizar el paquete DBMS_SQLPA (existe a partir de 11g R1) para realizar la comparación. Para el ejemplo los paquetes DBMS_SQLTUNE y DBMS_SQLPA son complementarios. Con el primero armo el workload (que puede contener una o mas sentencias obtenidas desde el AWR, desde un cursor, desde otro STS e incluso desde un archivo de trace) y con el segundo realizo el analisis comparativo (benchmarking). A continuación veamos como realizar dicho análisis: &lt;br /&gt;&lt;br /&gt;Primero creo una tarea de análisis:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;declare&lt;br /&gt;    l_out char(50);&lt;br /&gt;begin&lt;br /&gt;      l_out:= dbms_sqlpa.create_analysis_task(&lt;br /&gt;                   sqlset_name =&gt; 'Prueba',&lt;br /&gt;                   task_name   =&gt; 'Prueba_TSK');&lt;br /&gt;end;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Luego, configuro el ambiente para simular "el antes". En nuestro ejemplo la idea es comparar un count con RBO y con CBO, asi que seteo a  nivel sesión el optimizador para que use RBO y ejecuto el analisis con dicho entorno:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;alter session set optimizer_mode = RULE;&lt;br /&gt;&lt;br /&gt;begin&lt;br /&gt;  DBMS_SQLPA.EXECUTE_ANALYSIS_TASK(&lt;br /&gt;       task_name      =&gt; 'Prueba_TSK',&lt;br /&gt;       execution_type =&gt; 'TEST EXECUTE',&lt;br /&gt;       execution_name =&gt; 'Prueba_EXEC_antes');&lt;br /&gt;end;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Hago lo mismo para comparar "el despues", seteando el optimizador a su valor default en 11g:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;alter session set optimizer_mode = ALL_ROWS;&lt;br /&gt;&lt;br /&gt;begin &lt;br /&gt;  DBMS_SQLPA.EXECUTE_ANALYSIS_TASK(&lt;br /&gt;       task_name      =&gt; 'Prueba_TSK',&lt;br /&gt;       execution_type =&gt; 'TEST EXECUTE',&lt;br /&gt;       execution_name =&gt; 'Prueba_EXEC_despues');&lt;br /&gt;end;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Para realizar la comparación, se puede configurar sobre que metrica focalizarse, si no se aclara nada, se usa como metrica de comparación: "elapsed_time". En este ejemplo preferí usar "buffer_gets", ya que esta metrica es una de las que mas cambia entre los dos casos a comparar y por lo tanto hace mas contundente el reporte final.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;BEGIN&lt;br /&gt;DBMS_SQLPA.set_analysis_task_parameter('Prueba_TSK',&lt;br /&gt;                                     'comparison_metric', &lt;br /&gt;                                     'buffer_gets');&lt;br /&gt;END;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Ejectuo el sp para realizar la comparación:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;BEGIN&lt;br /&gt;  DBMS_SQLPA.execute_analysis_task(&lt;br /&gt;    task_name        =&gt; 'Prueba_TSK',&lt;br /&gt;    execution_type   =&gt; 'compare performance', &lt;br /&gt;    execution_params =&gt; dbms_advisor.arglist(&lt;br /&gt;                          'execution_name1', &lt;br /&gt;                          'Prueba_EXEC_antes', &lt;br /&gt;                          'execution_name2', &lt;br /&gt;                          'Prueba_EXEC_despues')&lt;br /&gt;    );&lt;br /&gt;END;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Una vez ejecutado el analisis vemos mediante un reporte un resumen de las diferencias:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SET LONG 1000000&lt;br /&gt;SET PAGESIZE 0&lt;br /&gt;SET LINESIZE 200&lt;br /&gt;SET LONGCHUNKSIZE 200&lt;br /&gt;SET TRIMSPOOL ON&lt;br /&gt;&lt;br /&gt;rop@DESA11G&gt; SELECT DBMS_SQLPA.REPORT_ANALYSIS_TASK('Prueba_TSK', 'TEXT', 'TYPICAL', 'SUMMARY') from&lt;br /&gt; dual;&lt;br /&gt;General Information&lt;br /&gt;---------------------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt; Task Information:                              Workload Information:&lt;br /&gt; ---------------------------------------------  ---------------------------------------------&lt;br /&gt;  Task Name    : Prueba_TSK                      SQL Tuning Set Name        : Prueba&lt;br /&gt;  Task Owner   : ROP                             SQL Tuning Set Owner       : ROP&lt;br /&gt;  Description  :                                 Total SQL Statement Count  : 1&lt;br /&gt;&lt;br /&gt;Execution Information:&lt;br /&gt;---------------------------------------------------------------------------------------------&lt;br /&gt;  Execution Name  : EXEC_7692              Started             : 08/12/2010 16:27:22&lt;br /&gt;  Execution Type  : COMPARE PERFORMANCE    Last Updated        : 08/12/2010 16:27:22&lt;br /&gt;  Description     :                        Global Time Limit   : UNLIMITED&lt;br /&gt;  Scope           : COMPREHENSIVE          Per-SQL Time Limit  : UNUSED&lt;br /&gt;  Status          : COMPLETED              Number of Errors    : 0&lt;br /&gt;&lt;br /&gt;Analysis Information:&lt;br /&gt;---------------------------------------------------------------------------------------------&lt;br /&gt; Comparison Metric: BUFFER_GETS&lt;br /&gt; ------------------&lt;br /&gt; Workload Impact Threshold: 1%&lt;br /&gt; --------------------------&lt;br /&gt; SQL Impact Threshold: 1%&lt;br /&gt; ----------------------&lt;br /&gt; Before Change Execution:                       After Change Execution:&lt;br /&gt; ---------------------------------------------  ---------------------------------------------&lt;br /&gt;  Execution Name      : Prueba_EXEC_antes        Execution Name      : Prueba_EXEC_despues&lt;br /&gt;  Execution Type      : TEST EXECUTE             Execution Type      : TEST EXECUTE&lt;br /&gt;  Scope               : COMPREHENSIVE            Scope               : COMPREHENSIVE&lt;br /&gt;  Status              : COMPLETED                Status              : COMPLETED&lt;br /&gt;  Started             : 08/12/2010 16:26:29      Started             : 08/12/2010 16:27:04&lt;br /&gt;  Last Updated        : 08/12/2010 16:27:13      Last Updated        : 08/12/2010 16:27:13&lt;br /&gt;  Global Time Limit   : UNLIMITED                Global Time Limit   : UNLIMITED&lt;br /&gt;  Per-SQL Time Limit  : UNUSED                   Per-SQL Time Limit  : UNUSED&lt;br /&gt;  Number of Errors    : 0                        Number of Errors    : 0&lt;br /&gt;&lt;br /&gt;Report Summary&lt;br /&gt;---------------------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Projected Workload Change Impact:&lt;br /&gt;-------------------------------------------&lt;br /&gt; Overall Impact      :  92.05%&lt;br /&gt; Improvement Impact  :  92.05%&lt;br /&gt; Regression Impact   :  0%&lt;br /&gt;&lt;br /&gt;SQL Statement Count&lt;br /&gt;-------------------------------------------&lt;br /&gt; SQL Category  SQL Count  Plan Change Count&lt;br /&gt; Overall               1                  1&lt;br /&gt; Improved              1                  1&lt;br /&gt;&lt;br /&gt;Top SQL Statements Sorted by their Absolute Value of Change Impact on the Workload&lt;br /&gt;--------------------------------------------------------------------------------------&lt;br /&gt;----------------------------------------------------------------------------------------------------&lt;br /&gt;|           |               | Impact on | Metric | Metric | Impact | % Workload | % Workload | Plan   |&lt;br /&gt;| object_id | sql_id        | Workload  | Before | After  | on SQL | Before     | After      | Change |&lt;br /&gt;----------------------------------------------------------------------------------------------------&lt;br /&gt;|         4 | 5r2ufj2vqkk4p |    92.05% |  26429 |   2102 | 92.05% |       100% |       100% | y      |&lt;br /&gt;----------------------------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;---------------------------------------------------------------------------------------------&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Como se observa en el reporte, con CBO se tiene una mejora del 92%&lt;br /&gt;&lt;br /&gt;Otro reporte con detalle de cada sentencia:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Transcurrido: 00:00:00.32&lt;br /&gt;rop@DESA11G&gt; SELECT DBMS_SQLPA.REPORT_ANALYSIS_TASK('Prueba_TSK', &lt;br /&gt;            'TEXT', 'TYPICAL', 'FINDINGS') from dual;&lt;br /&gt;General Information&lt;br /&gt;---------------------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt; Task Information:                              Workload Information:&lt;br /&gt; ---------------------------------------------  ---------------------------------------------&lt;br /&gt;  Task Name    : Prueba_TSK                      SQL Tuning Set Name        : Prueba&lt;br /&gt;  Task Owner   : ROP                             SQL Tuning Set Owner       : ROP&lt;br /&gt;  Description  :                                 Total SQL Statement Count  : 1&lt;br /&gt;&lt;br /&gt;Execution Information:&lt;br /&gt;---------------------------------------------------------------------------------------------&lt;br /&gt;  Execution Name  : EXEC_7692              Started             : 08/12/2010 16:27:22&lt;br /&gt;  Execution Type  : COMPARE PERFORMANCE    Last Updated        : 08/12/2010 16:27:22&lt;br /&gt;  Description     :                        Global Time Limit   : UNLIMITED&lt;br /&gt;  Scope           : COMPREHENSIVE          Per-SQL Time Limit  : UNUSED&lt;br /&gt;  Status          : COMPLETED              Number of Errors    : 0&lt;br /&gt;&lt;br /&gt;Analysis Information:&lt;br /&gt;---------------------------------------------------------------------------------------------&lt;br /&gt; Comparison Metric: BUFFER_GETS&lt;br /&gt; ------------------&lt;br /&gt; Workload Impact Threshold: 1%&lt;br /&gt; --------------------------&lt;br /&gt; SQL Impact Threshold: 1%&lt;br /&gt; ----------------------&lt;br /&gt; Before Change Execution:                       After Change Execution:&lt;br /&gt; ---------------------------------------------  ---------------------------------------------&lt;br /&gt;  Execution Name      : Prueba_EXEC_antes        Execution Name      : Prueba_EXEC_despues&lt;br /&gt;  Execution Type      : TEST EXECUTE             Execution Type      : TEST EXECUTE&lt;br /&gt;  Scope               : COMPREHENSIVE            Scope               : COMPREHENSIVE&lt;br /&gt;  Status              : COMPLETED                Status              : COMPLETED&lt;br /&gt;  Started             : 08/12/2010 16:26:29      Started             : 08/12/2010 16:27:04&lt;br /&gt;  Last Updated        : 08/12/2010 16:27:13      Last Updated        : 08/12/2010 16:27:13&lt;br /&gt;  Global Time Limit   : UNLIMITED                Global Time Limit   : UNLIMITED&lt;br /&gt;  Per-SQL Time Limit  : UNUSED                   Per-SQL Time Limit  : UNUSED&lt;br /&gt;  Number of Errors    : 0                        Number of Errors    : 0&lt;br /&gt;&lt;br /&gt;Report Details: Statements Sorted by their Absolute Value of Change Impact on the Workload&lt;br /&gt;---------------------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;SQL Details:&lt;br /&gt;-----------------------------&lt;br /&gt; Object ID            : 4&lt;br /&gt; Schema Name          : ROP&lt;br /&gt; SQL ID               : 5r2ufj2vqkk4p&lt;br /&gt; Execution Frequency  : 1&lt;br /&gt; SQL Text             : select count(1) /*+ Prueba SPA */ from t&lt;br /&gt;&lt;br /&gt;Execution Statistics:&lt;br /&gt;-----------------------------&lt;br /&gt;------------------------------------------------------------------------------------------------&lt;br /&gt;|                       | Impact on | Value     | Value    | Impact  | % Workload | % Workload |&lt;br /&gt;| Stat Name             | Workload  | Before    | After    | on SQL  | Before     | After      |&lt;br /&gt;------------------------------------------------------------------------------------------------&lt;br /&gt;| elapsed_time          |    91.11% |    15.477 |    1.376 |  91.11% |       100% |       100% |&lt;br /&gt;| parse_time            |    -1900% |         0 |     .019 |   -1.9% |         0% |       100% |&lt;br /&gt;| cpu_time              |     82.2% |      1.18 |      .21 |   82.2% |       100% |       100% |&lt;br /&gt;| buffer_gets           |    92.05% |     26429 |     2102 |  92.05% |       100% |       100% |&lt;br /&gt;| cost                  |   -59500% |         0 |      595 | -59500% |         0% |       100% |&lt;br /&gt;| reads                 |    92.08% |     26418 |     2092 |  92.08% |       100% |       100% |&lt;br /&gt;| writes                |        0% |         0 |        0 |      0% |         0% |         0% |&lt;br /&gt;| io_interconnect_bytes |    92.08% | 216416256 | 17137664 |  92.08% |       100% |       100% |&lt;br /&gt;| rows                  |           |         1 |        1 |         |            |            |&lt;br /&gt;------------------------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Findings (3):&lt;br /&gt;-----------------------------&lt;br /&gt; 1. Ha mejorado el rendimiento de este SQL.&lt;br /&gt; 2. La estructura del plan de ejecución SQL ha cambiado.&lt;br /&gt; 3. La estructura del plan de ejecución SQL de la versión anterior de la carga&lt;br /&gt;    de trabajo es distinta del correspondiente plan almacenado en el juego de&lt;br /&gt;    ajustes SQL.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Execution Plan Before Change:&lt;br /&gt;-----------------------------&lt;br /&gt; Plan Id          : 10810&lt;br /&gt; Plan Hash Value  : 1842905362&lt;br /&gt;&lt;br /&gt;-----------------------------------------------------------------&lt;br /&gt;| Id | Operation            | Name | Rows | Bytes | Cost | Time |&lt;br /&gt;-----------------------------------------------------------------&lt;br /&gt;|  0 | SELECT STATEMENT     |      |      |       |      |      |&lt;br /&gt;|  1 |   SORT AGGREGATE     |      |      |       |      |      |&lt;br /&gt;|  2 |    TABLE ACCESS FULL | T    |      |       |      |      |&lt;br /&gt;-----------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Execution Plan After Change:&lt;br /&gt;-----------------------------&lt;br /&gt; Plan Id          : 10811&lt;br /&gt; Plan Hash Value  : 2499172778&lt;br /&gt;&lt;br /&gt;----------------------------------------------------------------------------------&lt;br /&gt;| Id | Operation               | Name         | Rows   | Bytes | Cost | Time     |&lt;br /&gt;----------------------------------------------------------------------------------&lt;br /&gt;|  0 | SELECT STATEMENT        |              |      1 |       |  595 | 00:00:08 |&lt;br /&gt;|  1 |   SORT AGGREGATE        |              |      1 |       |      |          |&lt;br /&gt;|  2 |    INDEX FAST FULL SCAN | SYS_C0046154 | 908242 |       |  595 | 00:00:08 |&lt;br /&gt;----------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Note&lt;br /&gt;-----&lt;br /&gt;- dynamic sampling used for this statement&lt;br /&gt;&lt;br /&gt;---------------------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Transcurrido: 00:00:00.59&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;El ejemplo realiza una comparación trivial del procedimiento de SPA usando solo sqlplus (se podria usar EM para una comparación mas visual), pero que sirve para graficar su utilidad. Usando un procedimiento similar se podria realizar un analisis pre-upgrade que sirva para garantizar la estabilidad de las sentencias criticas post-upgrade sobre una base 11g. Para ello habria que realizar los siguiente pasos previos al analisis con SPA:&lt;br /&gt;&lt;br /&gt;1. Crear el STS en la base a upgradear&lt;br /&gt;&lt;br /&gt;Si la base a upgradear es 10g se puede crear el STS desde AWR determinando un intervalo representativo de la carga. Si la base a upgradear es 9i se puede obtener el STS desde un trace previamente generado en 9i durante un intervalo de carga real.&lt;br /&gt;&lt;br /&gt;A continuación, muestro un ejemplo, que esta en la documentación oficial 10g, para&lt;br /&gt;crear un STS desde AWR, usando un baseline previamente creado correspondiente a un intervalo con carga maxima "peak baseline", y se filtra para que el STS solo incluya las sentencias que se ejecutaron mas de 10 veces y con un ratio entre lecturas de disco y buffer gets mayor al 50%. Tambien se especifica que se recolecten las 30 sentencias TOP ordenadas por disk_reads/buffer_gets:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;DECLARE&lt;br /&gt; baseline_cursor DBMS_SQLTUNE.SQLSET_CURSOR;&lt;br /&gt;BEGIN&lt;br /&gt; OPEN baseline_cursor FOR&lt;br /&gt;    SELECT VALUE(p)&lt;br /&gt;    FROM TABLE (DBMS_SQLTUNE.SELECT_WORKLOAD_REPOSITORY(&lt;br /&gt;                  'peak baseline',&lt;br /&gt;                  'executions &gt;= 10 AND disk_reads/buffer_gets &gt;= 0.5',&lt;br /&gt;                   NULL,&lt;br /&gt;                   'disk_reads/buffer_gets',&lt;br /&gt;                   NULL, NULL, NULL,&lt;br /&gt;                   30)) p;&lt;br /&gt;&lt;br /&gt;    DBMS_SQLTUNE.LOAD_SQLSET(&lt;br /&gt;             sqlset_name     =&gt; 'my_sql_tuning_set',&lt;br /&gt;             populate_cursor =&gt; baseline_cursor);&lt;br /&gt;END;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Si no se creo un baseline, tambien se puede parametrizar usando dos snapshosts id de AWR para especificar el intervalo a procesar.&lt;br /&gt;&lt;br /&gt;2. Migrar el STS a la nueva base (11g)&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;-- Crea la tabla stage para almacenar el STS para luego transferirlo a la nueva base&lt;br /&gt;begin&lt;br /&gt;    DBMS_SQLTUNE.create_stgtab_sqlset(table_name =&gt; 'TBL_STG_STS',&lt;br /&gt;                                      schema_name =&gt; user);&lt;br /&gt;end;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;-- Graba el STS en la tabla stage&lt;br /&gt;begin&lt;br /&gt;    DBMS_SQLTUNE.pack_stgtab_sqlset(sqlset_name =&gt; 'Prueba',&lt;br /&gt;                                    staging_table_name =&gt; 'TBL_STG_STS'); &lt;br /&gt;end;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Una vez creada y cargada la tabla stage, resta pasarla a la nueva base. Aca se puede usar data pump o el exp/imp convencional.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;--  Crea el STS generado en 10g desde la tabla stage en 11g&lt;br /&gt;begin&lt;br /&gt;    DBMS_SQLTUNE.unpack_stgtab_sqlset(sqlset_name =&gt; 'Prueba',&lt;br /&gt;                                      staging_table_name =&gt; 'TBL_STG_STS',&lt;br /&gt;                                      replace =&gt; TRUE); &lt;br /&gt;end;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;En resumen, podriamos usar este procedimiento para evaluar rapidamente el impacto de cambios sobre las sentencias, y por ende en los planes de ejecución, producto de realizar cambios en el entorno, como por ejemplo, cambiar de equipo, de discos, agregar cpu, cambios de version de base de datos, cambios en parametrizacion, etc.&lt;br /&gt;Se puede "jugar" con distintos entornos y ver como se comporta las sentencias, realizar benchmarking y analisis con diferentes estrategias y parametrizaciones, etc y asi poder inferir el comportamiento previo al cambio y prevenir la inestabilidad de las aplicaciones cuando ya es demasiado tarde y la vuelta atras implica un alto costo.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5727738329548735676-4842239154947375875?l=oramdq.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://oramdq.blogspot.com/feeds/4842239154947375875/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://oramdq.blogspot.com/2010/08/benchmarking-de-performance-de.html#comment-form' title='0 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/4842239154947375875'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/4842239154947375875'/><link rel='alternate' type='text/html' href='http://oramdq.blogspot.com/2010/08/benchmarking-de-performance-de.html' title='Benchmarking de performance de sentencias usando SQL Performance Analyzer  (SPA)'/><author><name>Pablo A. Rovedo</name><uri>http://www.blogger.com/profile/02924687287216878757</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://3.bp.blogspot.com/_scsqOe7UOdc/SYDOV9ANLfI/AAAAAAAAAFQ/71M2XO1AZ1M/S220/DSC02067+(Small).JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5727738329548735676.post-5987839554192366970</id><published>2010-07-30T11:59:00.000-07:00</published><updated>2010-08-01T06:17:38.900-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Upgrade'/><title type='text'>Como realizar un Upgrade Manual desde versión 10g a 11g (Procedimiento Paso a Paso)</title><content type='html'>&lt;meta equiv="Content-Type" content="text/html; charset=utf-8"&gt;&lt;meta name="ProgId" content="Word.Document"&gt;&lt;meta name="Generator" content="Microsoft Word 11"&gt;&lt;meta name="Originator" content="Microsoft Word 11"&gt;&lt;link rel="File-List" href="file:///C:%5CDOCUME%7E1%5Cprovedo%5CLOCALS%7E1%5CTemp%5Cmsohtml1%5C01%5Cclip_filelist.xml"&gt;&lt;!--[if gte mso 9]&gt;&lt;xml&gt;  &lt;w:worddocument&gt;   &lt;w:view&gt;Normal&lt;/w:View&gt;   &lt;w:zoom&gt;0&lt;/w:Zoom&gt;   &lt;w:hyphenationzone&gt;21&lt;/w:HyphenationZone&gt;   &lt;w:punctuationkerning/&gt;   &lt;w:validateagainstschemas/&gt;   &lt;w:saveifxmlinvalid&gt;false&lt;/w:SaveIfXMLInvalid&gt;   &lt;w:ignoremixedcontent&gt;false&lt;/w:IgnoreMixedContent&gt;   &lt;w:alwaysshowplaceholdertext&gt;false&lt;/w:AlwaysShowPlaceholderText&gt;   &lt;w:compatibility&gt;    &lt;w:breakwrappedtables/&gt;    &lt;w:snaptogridincell/&gt;    &lt;w:wraptextwithpunct/&gt;    &lt;w:useasianbreakrules/&gt;    &lt;w:dontgrowautofit/&gt;   &lt;/w:Compatibility&gt;   &lt;w:browserlevel&gt;MicrosoftInternetExplorer4&lt;/w:BrowserLevel&gt;  &lt;/w:WordDocument&gt; &lt;/xml&gt;&lt;![endif]--&gt;&lt;!--[if gte mso 9]&gt;&lt;xml&gt;  &lt;w:latentstyles deflockedstate="false" latentstylecount="156"&gt;  &lt;/w:LatentStyles&gt; &lt;/xml&gt;&lt;![endif]--&gt;&lt;style&gt; &lt;!--  /* Font Definitions */  @font-face 	{font-family:Wingdings; 	panose-1:5 0 0 0 0 0 0 0 0 0; 	mso-font-charset:2; 	mso-generic-font-family:auto; 	mso-font-pitch:variable; 	mso-font-signature:0 268435456 0 0 -2147483648 0;} @font-face 	{font-family:Verdana; 	panose-1:2 11 6 4 3 5 4 4 2 4; 	mso-font-charset:0; 	mso-generic-font-family:swiss; 	mso-font-pitch:variable; 	mso-font-signature:536871559 0 0 0 415 0;}  /* Style Definitions */  p.MsoNormal, li.MsoNormal, div.MsoNormal 	{mso-style-parent:""; 	margin:0cm; 	margin-bottom:.0001pt; 	mso-pagination:widow-orphan; 	font-size:12.0pt; 	font-family:"Times New Roman"; 	mso-fareast-font-family:"Times New Roman";} h2 	{mso-style-parent:"Texto independiente"; 	mso-style-next:"Texto independiente"; 	margin-top:6.0pt; 	margin-right:0cm; 	margin-bottom:6.0pt; 	margin-left:0cm; 	page-break-before:always; 	mso-pagination:widow-orphan lines-together; 	page-break-after:avoid; 	mso-outline-level:2; 	border:none; 	mso-border-top-alt:solid windowtext 3.75pt; 	padding:0cm; 	mso-padding-alt:4.0pt 0cm 0cm 0cm; 	font-size:14.0pt; 	mso-bidi-font-size:12.0pt; 	font-family:"Times New Roman"; 	mso-bidi-font-weight:normal;} h4 	{mso-style-parent:"Texto independiente"; 	mso-style-next:"Texto independiente"; 	margin-top:12.0pt; 	margin-right:0cm; 	margin-bottom:0cm; 	margin-left:126.0pt; 	margin-bottom:.0001pt; 	mso-pagination:widow-orphan lines-together; 	page-break-after:avoid; 	mso-outline-level:4; 	tab-stops:center 324.0pt right 522.0pt; 	border:none; 	mso-border-bottom-alt:solid windowtext .75pt; 	padding:0cm; 	mso-padding-alt:0cm 0cm 1.0pt 0cm; 	font-size:12.0pt; 	font-family:"Times New Roman"; 	mso-bidi-font-weight:normal;} p.MsoBodyText, li.MsoBodyText, div.MsoBodyText 	{margin-top:6.0pt; 	margin-right:0cm; 	margin-bottom:6.0pt; 	margin-left:126.0pt; 	mso-pagination:widow-orphan; 	font-size:12.0pt; 	font-family:"Times New Roman"; 	mso-fareast-font-family:"Times New Roman";} @page Section1 	{size:612.0pt 792.0pt; 	margin:70.85pt 3.0cm 70.85pt 3.0cm; 	mso-header-margin:36.0pt; 	mso-footer-margin:36.0pt; 	mso-paper-source:0;} div.Section1 	{page:Section1;}  /* List Definitions */  @list l0 	{mso-list-id:100539876; 	mso-list-type:hybrid; 	mso-list-template-ids:2005943010 201981967 1979878772 201981967 201981967 201981977 201981979 201981967 201981977 201981979;} @list l0:level1 	{mso-level-tab-stop:174.0pt; 	mso-level-number-position:left; 	margin-left:174.0pt; 	text-indent:-18.0pt;} @list l0:level2 	{mso-level-start-at:5; 	mso-level-text:%2; 	mso-level-tab-stop:210.0pt; 	mso-level-number-position:left; 	margin-left:210.0pt; 	text-indent:-18.0pt;} @list l0:level3 	{mso-level-tab-stop:255.0pt; 	mso-level-number-position:left; 	margin-left:255.0pt; 	text-indent:-18.0pt;} @list l1 	{mso-list-id:111245900; 	mso-list-type:hybrid; 	mso-list-template-ids:1153102224 201981953 201981955 201981957 201981953 201981955 201981957 201981953 201981955 201981957;} @list l1:level1 	{mso-level-number-format:bullet; 	mso-level-text:; 	mso-level-tab-stop:189.0pt; 	mso-level-number-position:left; 	margin-left:189.0pt; 	text-indent:-18.0pt; 	font-family:Symbol;} @list l2 	{mso-list-id:1252465592; 	mso-list-type:hybrid; 	mso-list-template-ids:-1963312150 738852879 738852867 738852869 738852865 738852867 738852869 738852865 738852867 738852869;} @list l2:level1 	{mso-level-tab-stop:36.0pt; 	mso-level-number-position:left; 	text-indent:-18.0pt;} @list l2:level4 	{mso-level-number-format:bullet; 	mso-level-text:; 	mso-level-tab-stop:144.0pt; 	mso-level-number-position:left; 	text-indent:-18.0pt; 	font-family:Symbol;} ol 	{margin-bottom:0cm;} ul 	{margin-bottom:0cm;} --&gt; &lt;/style&gt;&lt;!--[if gte mso 10]&gt; &lt;style&gt;  /* Style Definitions */  table.MsoNormalTable 	{mso-style-name:"Tabla normal"; 	mso-tstyle-rowband-size:0; 	mso-tstyle-colband-size:0; 	mso-style-noshow:yes; 	mso-style-parent:""; 	mso-padding-alt:0cm 5.4pt 0cm 5.4pt; 	mso-para-margin:0cm; 	mso-para-margin-bottom:.0001pt; 	mso-pagination:widow-orphan; 	font-size:10.0pt; 	font-family:"Times New Roman"; 	mso-ansi-language:#0400; 	mso-fareast-language:#0400; 	mso-bidi-language:#0400;} &lt;/style&gt; &lt;![endif]--&gt;&lt;br /&gt;Para actualizar (upgrade)  una la versión de una base Oracle existen principalmente cuatro métodos:&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Usar el Asistente Grafico (DBUA). Es el metodo sugerido en los manuales.&lt;/li&gt;&lt;li&gt;Usar scripting o forma manual (la que yo siempre tiendo a usar)&lt;/li&gt;&lt;li&gt;Export/Import&lt;/li&gt;&lt;li&gt;CTAS (para mi gusto la mas complicada, hay que crear dblink, armar los scripts para el traspaso de los objetos, etc)&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;En esta nota voy a escribir un procedimiento paso a paso para actualizar usando el método manual (el 2 en la lista de arriba):&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;&lt;span style="font-weight: bold;"&gt;Pre-Upgrade&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;1. Si el upgrade es sobre el mismo equipo habria que instalar el software (el motor) 11g en un nuevo home. Se puede usar el mismo user oracle de la instalación 10g corriente y setear las variables de entorno para 11g o se podria usar un nuevo usuario oracle (ej: oracle11g).&lt;br /&gt;&lt;div style="text-align: left;"&gt;&lt;o:p&gt;&lt;br /&gt;2. Una vez instalado el software conectarse con el usuario oracle de la instalación 11g&lt;/o:p&gt; y copiar el archivo $ORACLE_HOME/rdbms/admin/utlu112i.sql a un directorio compartido (ej: /tmp).&lt;br /&gt;&lt;br /&gt;3. Conectarse con el usuario 10g (ej: oracle) o si se esta usando el mismo usuario setear las variables de ambiente para usar el motor 10g, para ejecutar el script copiado en el paso 2. Este script brinda información previa al upgrade que se usará para preparar la versión actual para que no haya problemas durante el upgrade&lt;br /&gt;&lt;br /&gt;4. Conectarse con sqlplus:&lt;br /&gt;&lt;div style="text-align: left;"&gt;     &lt;br /&gt;     &lt;span style="font-style: italic;"&gt;$ sqlplus / as sysdba&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;5. Setear el spool para que no quede registro de la ejecución del script&lt;br /&gt;&lt;br /&gt;     &lt;span style="font-style: italic;"&gt;sqlplus&gt; spool upg_info.log&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;6. Ejecutar el script:&lt;br /&gt;&lt;br /&gt;     &lt;span style="font-style: italic;"&gt;sqlplus&gt; @/tmp/utlu112i.sql;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;7. Desactivar el spool:&lt;br /&gt;&lt;br /&gt;    &lt;span style="font-style: italic;"&gt;sqlplus&gt; spool off&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/div&gt;Ejemplo de Salida del Reporte de información pre-upgrade&lt;br /&gt;&lt;br /&gt;A continuación se muestra una salida típica del script utlu112i.sql sobre una base llamada ROP10g sobre un equipo Solaris 10.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;Oracle Database 11.2 Pre-Upgrade Information Tool    07-26-2010 15:14:36&lt;br /&gt;**********************************************************************&lt;br /&gt;Database:&lt;br /&gt;**********************************************************************&lt;br /&gt;&lt;br /&gt;--&gt; name:          ROP10G&lt;br /&gt;&lt;br /&gt;--&gt; version:       10.2.0.4.0&lt;br /&gt;&lt;br /&gt;--&gt; compatible:    10.2.0.1.0&lt;br /&gt;&lt;br /&gt;--&gt; blocksize:     8192&lt;br /&gt;&lt;br /&gt;--&gt; platform:      Solaris[tm] OE (64-bit)&lt;br /&gt;&lt;br /&gt;--&gt; timezone file: V4&lt;br /&gt;&lt;br /&gt;**********************************************************************&lt;br /&gt;Tablespaces: [make adjustments in the current environment]&lt;br /&gt;&lt;br /&gt;**********************************************************************&lt;br /&gt;&lt;br /&gt;--&gt; SYSTEM tablespace is adequate for the upgrade.&lt;br /&gt;&lt;br /&gt;.... minimum required size: 313 MB&lt;br /&gt;&lt;br /&gt;.... AUTOEXTEND additional space required: 83 MB&lt;br /&gt;&lt;br /&gt;--&gt; UNDO tablespace is adequate for the upgrade.&lt;br /&gt;&lt;br /&gt;.... minimum required size: 121 MB&lt;br /&gt;&lt;br /&gt;--&gt; SYSAUX tablespace is adequate for the upgrade.&lt;br /&gt;&lt;br /&gt;.... minimum required size: 73 MB&lt;br /&gt;&lt;br /&gt;.... AUTOEXTEND additional space required: 23 MB&lt;br /&gt;&lt;br /&gt;--&gt; TEMP tablespace is adequate for the upgrade.&lt;br /&gt;&lt;br /&gt;.... minimum required size: 61 MB&lt;br /&gt;&lt;br /&gt;**********************************************************************&lt;br /&gt;&lt;br /&gt;Update Parameters: [Update Oracle Database 11.2 init.ora or spfile]&lt;br /&gt;&lt;br /&gt;**********************************************************************&lt;br /&gt;&lt;br /&gt;WARNING: --&gt; "sga_target" needs to be increased to at least 672 MB&lt;br /&gt;&lt;br /&gt;**********************************************************************&lt;br /&gt;&lt;br /&gt;Renamed Parameters: [Update Oracle Database 11.2 init.ora or spfile]&lt;br /&gt;&lt;br /&gt;**********************************************************************&lt;br /&gt;&lt;br /&gt;-- No renamed parameters found. No changes are required.&lt;br /&gt;&lt;br /&gt;**********************************************************************&lt;br /&gt;&lt;br /&gt;Obsolete/Deprecated Parameters: [Update Oracle Database 11.2 init.ora or spfile]&lt;br /&gt;&lt;br /&gt;**********************************************************************&lt;br /&gt;&lt;br /&gt;--&gt; "background_dump_dest" replaced by  "diagnostic_dest"&lt;br /&gt;&lt;br /&gt;--&gt; "user_dump_dest" replaced by  "diagnostic_dest"&lt;br /&gt;&lt;br /&gt;--&gt; "core_dump_dest" replaced by  "diagnostic_dest"&lt;br /&gt;&lt;br /&gt;**********************************************************************&lt;br /&gt;&lt;br /&gt;Components: [The following database components will be upgraded or installed]&lt;br /&gt;&lt;br /&gt;**********************************************************************&lt;br /&gt;&lt;br /&gt;--&gt; Oracle Catalog Views         [upgrade]  VALID&lt;br /&gt;&lt;br /&gt;--&gt; Oracle Packages and Types    [upgrade]  VALID&lt;br /&gt;&lt;br /&gt;**********************************************************************&lt;br /&gt;&lt;br /&gt;Miscellaneous Warnings&lt;br /&gt;&lt;br /&gt;**********************************************************************&lt;br /&gt;&lt;br /&gt;WARNING: --&gt; Database contains stale optimizer statistics.&lt;br /&gt;&lt;br /&gt;.... Refer to the 11g Upgrade Guide for instructions to update&lt;br /&gt;&lt;br /&gt;.... statistics prior to upgrading the database.&lt;br /&gt;&lt;br /&gt;.... Component Schemas with stale statistics:&lt;br /&gt;&lt;br /&gt;....   SYS&lt;br /&gt;&lt;br /&gt;WARNING: --&gt; Database contains INVALID objects prior to upgrade.&lt;br /&gt;&lt;br /&gt;.... The list of invalid SYS/SYSTEM objects was written to&lt;br /&gt;&lt;br /&gt;.... registry$sys_inv_objs.&lt;br /&gt;&lt;br /&gt;.... The list of non-SYS/SYSTEM objects was written to&lt;br /&gt;&lt;br /&gt;.... registry$nonsys_inv_objs.&lt;br /&gt;&lt;br /&gt;.... Use utluiobj.sql after the upgrade to identify any new invalid&lt;br /&gt;&lt;br /&gt;.... objects due to the upgrade.&lt;br /&gt;&lt;br /&gt;.... USER PUBLIC has 1 INVALID objects.&lt;br /&gt;&lt;br /&gt;.... USER SYS has 2 INVALID objects.&lt;br /&gt;....&lt;br /&gt;....&lt;br /&gt;SQL&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;Controles que se realizan con la herramienta de pre-upgrade&lt;br /&gt;&lt;br /&gt;        El script “utlu112i.sql” realiza el chequeo de los siguientes puntos:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Chequea si las estadísticas de diccionario están actualizadas.&lt;/li&gt;&lt;li&gt;Chequea si existen objetos invalidos.&lt;/li&gt;&lt;li&gt;Chequea si la configuración de SGA cumple con los requerimientos minimos en 11g.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Chequea si hay database links con passwords (11g encripta las passwords).&lt;/li&gt;&lt;li&gt;Se asegura que no haya archivos que necesiten recovery.&lt;/li&gt;&lt;li&gt;Se asegura que no haya archivos en modo backup.&lt;/li&gt;&lt;li&gt;Si el recyclebin esta activado chequea si esta vacio (si esta totalmente purgado).&lt;/li&gt;&lt;li&gt;Chequea si los archivos de timezone son de tipo 4 (los archivos que estan en $ORACLE_HOME/oracore/zoneinfo).&lt;/li&gt;&lt;li&gt;Revisa si hay refrescos de Vistas Materializadas pendientes.&lt;/li&gt;&lt;li&gt;Revisa si hay transacciones distribuidas pendientes.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Una vez que se corrigieron los warnings reportados por el script anterior se puede proceder a realizar el upgrade&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;&lt;span style="font-weight: bold;"&gt;Upgrade&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;1. Conectarse con el owner (usuario oracle) de la instancia 10g.&lt;br /&gt;&lt;br /&gt;1.1 Verificar que no haya procesos oracle con el mismo nombre de la instancia&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;$ps -ef | grep -i ora | grep -v grep&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;1.2 Verificar que las variables de ambiente esten bien configuradas&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;$env | grep -i ora&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;2. Crear archivo pfile desde el spfile y copiarlo a $ORACLE_HOME/dbs en el nuevo equipo&lt;br /&gt;&lt;br /&gt;3. Editar el pfile en el equipo nuevo y modificar los parametros que sea necesario (deprecated).&lt;br /&gt;&lt;br /&gt;4. Bajar la base 10g en modo normal, si no hubiera conexiones sino se podria bajar en modo transactional, para que deje que termine las transacciones, no deje que se abran nuevas conexiones y baje la base en modo consistente y que no requiere revover. Como caso extremo tambien se podria considerar bajar la base en modo immediate.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;sqlplus&gt; shutdown transactional&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;5. Si la base 11g esta en otro equipo hay que transferir los archivos de base de datos (datafiles,redologs,controlfiles) al nuevo equipo en el mismo directorio y con los mismos permisos (usar comando unix scp, ftp, algun mecanismo de copiado de logical groups, etc). Si se realizará el upgrade sobre el mismo equipo y sobre los mismos archivos de base de datos no hace falta hacer nada en este punto.&lt;br /&gt;&lt;br /&gt;6. Conectarse a la instancia 11g con sysdba&lt;br /&gt;&lt;br /&gt;  &lt;span style="font-style: italic;"&gt; $ sqlplus / as sysdba&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;7. Levantar la base 11g en modo upgrade&lt;br /&gt;&lt;br /&gt;   &lt;span style="font-style: italic;"&gt;SQPLUS&gt; STARTUP UPGRADE &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;8. Setear el spool&lt;br /&gt;&lt;br /&gt;   &lt;span style="font-style: italic;"&gt;SQLPLUS&gt; spool upgrade11g.log&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;9. Ejecutar el script para obtener la información pre-upgrade&lt;br /&gt;&lt;br /&gt;   &lt;span style="font-style: italic;"&gt;SQLPLUS&gt; @?/rdbms/admin/catupgrd.sql;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;10. Desactivar el spooling&lt;br /&gt;&lt;br /&gt;   &lt;span style="font-style: italic;"&gt;SQLPLUS&gt; spool off&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;11. Ejecutar el script utlu112s.sql para ver el resultado del upgrade.&lt;br /&gt;&lt;br /&gt;   &lt;span style="font-style: italic;"&gt;SQLPLUS&gt;@?/rdbms/admin/utlu112s.sql&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;12. Ejecutar el script utlrp.sql para recompilar stored procedures y clases java.&lt;br /&gt;&lt;br /&gt;   i)  &lt;span style="font-style: italic;"&gt;SQLPLUS&gt;@?/rdbms/admin/utlrp.sql&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;   ii) &lt;span style="font-style: italic;"&gt;SQLPLUS&gt;exec UTL_RECOMP.RECOMP_SERIAL ();&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;13. Verificar que todos las paquetes y clases java quedaron compiladas&lt;br /&gt;&lt;br /&gt;   &lt;span style="font-style: italic;"&gt;SQLPLUS&gt; select count(1) from dba_invalid_objects.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;14. Crear spfile desde el pfile original.&lt;br /&gt;&lt;br /&gt;  &lt;span style="font-style: italic;"&gt; SQLPLUS&gt;create spfile from pfile;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;15. Reiniciar la instancia en forma normal.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Ejemplo de Salida del Reporte de información post-upgrade&lt;br /&gt;&lt;br /&gt;A continuación se muestra una salida típica del script utlu112i.sql sobre una base llamada ROP10g sobre un equipo Solaris 10&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;span style="font-size:85%;"&gt;SQL&gt; @?/rdbms/admin/utlu112s.sql;&lt;br /&gt;&lt;br /&gt;Oracle Database 11.2 Post-Upgrade Status Tool           07-26-2010 14:32:12&lt;br /&gt;&lt;br /&gt;Component                                Status         Version  HH:MM:SS&lt;br /&gt;&lt;br /&gt;Oracle Server&lt;br /&gt;                                  VALID      11.2.0.1.0  00:24:02&lt;br /&gt;&lt;br /&gt;Gathering Statistics&lt;br /&gt;.                                                                00:01:32&lt;br /&gt;Total Upgrade Time: 00:25:36&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;&lt;span style="font-weight: bold;"&gt;Post-Upgade&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;1. Analizar password case-sensitive&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;SQLPLUS&gt;alter system set sec_case_sensitive_logon = false scope=both;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Lo ideal seria dejar este parametro en true (default) , ya que fortalece la seguridad, pero habría que analizar como afecta algunas aplicaciones (por ejemplo algunas versiones de TOAD no se podrán conectar)&lt;br /&gt;&lt;br /&gt;2. Setear el parametro COMPATIBLE a 11.2.0&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;SQLPLUS&gt;alter system set compatible = '11.2.0' scope=spfile;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;3. Habilitar los umbrales para alertas sobre tablespaces.&lt;br /&gt;&lt;br /&gt;4. Reiniciar la instancia&lt;br /&gt;&lt;br /&gt;5. Verificar conexión a través del  listener&lt;br /&gt;&lt;br /&gt;6. Dependiendo del tipo de backup y la herramienta que se use a veces es necesario cambiar la identificación interna de la base para que se tome como una nueva base&lt;br /&gt;&lt;br /&gt;Como Cambiar el dbid:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt; SQLPLUS&gt;shutdown immediate&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;       SQLPLUS&gt;startup mount&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;       $ nid TARGET=SYS &lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;       SQLPLUS&gt;startup mount&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;       SQLPLUS&gt;alter database open resetlogs&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;6  Backup full de la base de datos.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=""&gt;&lt;/span&gt;&lt;p class="MsoBodyText" style="margin-left: 174pt; text-indent: -18pt;"&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5727738329548735676-5987839554192366970?l=oramdq.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://oramdq.blogspot.com/feeds/5987839554192366970/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://oramdq.blogspot.com/2010/07/como-realizar-un-upgrade-manual-desde.html#comment-form' title='2 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/5987839554192366970'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/5987839554192366970'/><link rel='alternate' type='text/html' href='http://oramdq.blogspot.com/2010/07/como-realizar-un-upgrade-manual-desde.html' title='Como realizar un Upgrade Manual desde versión 10g a 11g (Procedimiento Paso a Paso)'/><author><name>Pablo A. Rovedo</name><uri>http://www.blogger.com/profile/02924687287216878757</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://3.bp.blogspot.com/_scsqOe7UOdc/SYDOV9ANLfI/AAAAAAAAAFQ/71M2XO1AZ1M/S220/DSC02067+(Small).JPG'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5727738329548735676.post-1866309350616394980</id><published>2010-06-11T04:57:00.000-07:00</published><updated>2010-06-11T11:47:45.435-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Conceptos'/><category scheme='http://www.blogger.com/atom/ns#' term='Procesamiento'/><category scheme='http://www.blogger.com/atom/ns#' term='Diseño'/><title type='text'>La importancia de ordenar adecuadamente las columnas cuando se define una tabla</title><content type='html'>Dependiendo del caso, hay que prestar suficiente atención en el orden en el que se definen las columnas en la etapa de diseño fisico de las tablas.  Para poder entender la situación que planteo, primero seria bueno que les muestre como almacena Oracle las filas en los bloques.&lt;br /&gt;&lt;br /&gt;Una fila se almacena en un bloque de la siguiente forma:&lt;br /&gt;&lt;br /&gt;Primero se define el Encabezado (H) que guarda propiedades acerca de la fila en si misma, tales como la cantidad de columnas que tiene y el flag que determina si esta lockeada. Luego vienen los datos en formato de duplas (largo de la columna,contenido de la columna). Como cada columna puede tener diferentes largos, cada una de ellas consta de dos partes: el largo Lx y los datos en si mismo Dx.  Dado que el motor de base de datos no conoce el offset de las columnas en la fila, tiene que comenzar desde la primera columna, ver el largo, desplazarse hasta donde se encuentra el dato del largo del segunda columna y asi siguiendo hasta encontrar la columna buscada. Abajo, les muestro como se guarda la fila:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_scsqOe7UOdc/TBI2acHdFPI/AAAAAAAAAKQ/oi-waWOy2_s/s1600/Rows_Storage.bmp"&gt;&lt;img style="cursor: pointer; width: 400px; height: 28px;" src="http://3.bp.blogspot.com/_scsqOe7UOdc/TBI2acHdFPI/AAAAAAAAAKQ/oi-waWOy2_s/s400/Rows_Storage.bmp" alt="" id="BLOGGER_PHOTO_ID_5481503524315272434" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Como se habrán dado cuenta, si se necesita buscar una columna que esta al final, Oracle tardará mucho mas que para buscar una columna del principio, este overhead no es despreciable y podría afectar la performance, sobre todo para aplicaciones con requerimientos de tiempos de respuesta muy bajos, del orden de los milisegundos.  Es por eso que en ciertos casos es recomendable definir al principio las columnas con mayor tasa de referencia y al final las que sean menos frecuentemente consultadas.&lt;br /&gt;Para que puedan observar el grado de impacto de un orden de columnas no optimo, voy a armar un ejemplo sencillo que se pueda entender mejor:&lt;br /&gt;&lt;br /&gt;Voy a crear una tabla T con 200 columnas de tipo INT, y luego las voy a insertar 5000 filas:&lt;br /&gt;&lt;br /&gt;Creo la tabla T con la primera columna X1&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;create table t (x1 int);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Para no escribir la ddl con las 200 columnas lo voy a hacer dinamicamente, agregando las 199 columnas restantes:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;begin&lt;br /&gt;for i in 2..200&lt;br /&gt;loop&lt;br /&gt;  execute immediate 'alter table t add x'||i||' int';&lt;br /&gt;end loop;&lt;br /&gt;end;&lt;br /&gt;/&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Ahora voy a insertar las 5000 filas, de forma tal de llenar todas las columnas con el mismo valor por fila.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;begin&lt;br /&gt;for i in 1..5000&lt;br /&gt;loop&lt;br /&gt;  insert into t(x1) values (i);&lt;br /&gt;  for j in 2..200&lt;br /&gt;  loop&lt;br /&gt;     execute immediate 'update t set x'||j||' = '||i||' where x1 = '||i;&lt;br /&gt;  end loop;&lt;br /&gt;  commit;&lt;br /&gt;end loop;&lt;br /&gt;end;&lt;br /&gt;/&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Una vez creada y populada la tabla T, voy a ejecutar un bloque anonimo que realiza 1000 veces la suma de todas las filas para cada columna Xn:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;declare&lt;br /&gt;l_cnt int;&lt;br /&gt;l_foo int;&lt;br /&gt;l_stime int;&lt;br /&gt;begin&lt;br /&gt;for i in 1..200&lt;br /&gt;loop&lt;br /&gt;   l_stime := dbms_utility.get_time();&lt;br /&gt;   for j in 1..1000&lt;br /&gt;   loop&lt;br /&gt;      execute immediate 'select sum(x'||i||') from t' into l_foo;&lt;br /&gt;   end loop;&lt;br /&gt;   insert into t2 values (i,dbms_utility.get_time()-l_stime));&lt;br /&gt;   commit;&lt;br /&gt;end loop;&lt;br /&gt;end;&lt;br /&gt;/&lt;br /&gt;&lt;/pre&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_scsqOe7UOdc/TBJQZtwk6GI/AAAAAAAAAKY/vJLHehnd2G0/s1600/Rows_Storage_Curva.bmp"&gt;&lt;img style="cursor: pointer; width: 400px; height: 233px;" src="http://3.bp.blogspot.com/_scsqOe7UOdc/TBJQZtwk6GI/AAAAAAAAAKY/vJLHehnd2G0/s400/Rows_Storage_Curva.bmp" alt="" id="BLOGGER_PHOTO_ID_5481532099173607522" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Curva de Comparación&lt;br /&gt;&lt;br /&gt;En la curva de arriba el eje X mide la posición de la columna en la fila y el eje Y el tiempo de procesamiento en segundos (usando el bloque pl de arriba) para operar con la columna. Como se puede apreciar el tiempo de procesamiento es directamente proporcional a la ubicación de la columna en la fila.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5727738329548735676-1866309350616394980?l=oramdq.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://oramdq.blogspot.com/feeds/1866309350616394980/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://oramdq.blogspot.com/2010/06/la-importancia-de-ordenar-adecuadamente.html#comment-form' title='1 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/1866309350616394980'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/1866309350616394980'/><link rel='alternate' type='text/html' href='http://oramdq.blogspot.com/2010/06/la-importancia-de-ordenar-adecuadamente.html' title='La importancia de ordenar adecuadamente las columnas cuando se define una tabla'/><author><name>Pablo A. Rovedo</name><uri>http://www.blogger.com/profile/02924687287216878757</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://3.bp.blogspot.com/_scsqOe7UOdc/SYDOV9ANLfI/AAAAAAAAAFQ/71M2XO1AZ1M/S220/DSC02067+(Small).JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_scsqOe7UOdc/TBI2acHdFPI/AAAAAAAAAKQ/oi-waWOy2_s/s72-c/Rows_Storage.bmp' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5727738329548735676.post-8765806105470090363</id><published>2010-04-30T07:36:00.000-07:00</published><updated>2011-08-31T18:34:16.884-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Monitoreo'/><category scheme='http://www.blogger.com/atom/ns#' term='Diagnóstico'/><category scheme='http://www.blogger.com/atom/ns#' term='Tuning'/><category scheme='http://www.blogger.com/atom/ns#' term='Performance'/><title type='text'>Herramienta para diagnosticar problemas de performance (Oracle Performance Viewer Freeware)</title><content type='html'>&lt;div&gt;(Ahora podes bajarte el utilitario desde &lt;a href="http://www.oramdq.com/oracle-performance-viewer/"&gt;http://www.oramdq.com/oracle-performance-viewer/&lt;/a&gt;)&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;Hace ya varios años que trabajo con bases de datos Oracle y en todo ese tiempo fui acumulando scripts que me facilitaron diversas tareas de administración, mantenimiento, monitoreo, etc. Ya que ultimamente me vengo dedicando a temas de performance, me di cuenta que en ese tipo de actividad es importante analizar la mayor cantidad de información en el menor tiempo posible, recordemos que Oracle es una base altamente instrumentada y que versión tras versión se van agregando mas métricas (Oracle Wait Interface).  A partir de 10g tenemos el repositorio AWR que almacena historial detallado de la actividad de la base.&lt;br /&gt;&lt;br /&gt;El AWR resulta de mucha utilidad como baseline o punto de comparación cuando nos encontramos con una base que esta con problemas de rendimiento, detectados o bien por la activación de alarmas o peor aún, cuando el usuario final persive la demora y realiza el reclamo. Cualquiera que trabaje como DBA de bases productivas, ni hablar si son muy criticas, tendrá que responder rapidamente a su superior evaluando el escenario, diagnosticando y proponiendo o activando cursos de acción en forma inmediata. A veces los problemas no se detectan con facilidad y empezar a correr scripts por separado puede resultar un tanto lento.&lt;br /&gt;&lt;br /&gt;Para facilitarme la tarea, programé un pequeño utilitario en C# donde agregué varias de las consultas que utilizo diariamente, sumado a todo el poder gráfico que me permite entre otras cosas analizar la historia, filtrar convenientemente, exportar a excel la grilla, explotar la información con doble click sobre la celda, etc. Abajo copié algunas pantallas para mostrarles como esta pensada la aplicación y obviamente me interesa compartirla en forma gratuita con quien le interesa y asi poder mejorarla.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;La pantalla principal es una MDI con 4 paneles, el de arriba a la izquierda tiene una estructura de árbol con todos los reportes disponibles hasta el momento, clasificados según cierto criterio. el panel de abajo a la izquierda tiene un resumen de la base de datos donde esta conectada la app. el panel de arriba a la derecha tiene la grilla con los resultados, y el panel de abajo a la derecha tiene la consulta que se ejecutó para llenar la grilla.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://1.bp.blogspot.com/_scsqOe7UOdc/S9rr-lUOm3I/AAAAAAAAAJk/qSTctL4T8oE/s1600/Pantalla_Principal.bmp" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img style="width: 400px; height: 300px; cursor: pointer;" id="BLOGGER_PHOTO_ID_5465940558168890226" alt="" src="http://1.bp.blogspot.com/_scsqOe7UOdc/S9rr-lUOm3I/AAAAAAAAAJk/qSTctL4T8oE/s400/Pantalla_Principal.bmp" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;La pantalla 2 muestra los criterios de filtrado para llenar la grilla de acuerdo al tipo de reporte que se este ejecutando:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://2.bp.blogspot.com/_scsqOe7UOdc/S9rr5Q3Q1YI/AAAAAAAAAJc/AW3aHFD95Uk/s1600/P2.bmp" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img style="width: 400px; height: 300px; cursor: pointer;" id="BLOGGER_PHOTO_ID_5465940466779346306" alt="" src="http://2.bp.blogspot.com/_scsqOe7UOdc/S9rr5Q3Q1YI/AAAAAAAAAJc/AW3aHFD95Uk/s400/P2.bmp" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;La pantalla 3 muestra la grilla resultado. En el ejemplo se ejecutó el reporte de historial de DB Time:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://4.bp.blogspot.com/_scsqOe7UOdc/S9rrt3J83tI/AAAAAAAAAJU/wdmPsoo-va4/s1600/p3.gif" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img style="width: 400px; height: 300px; cursor: pointer;" id="BLOGGER_PHOTO_ID_5465940270899846866" alt="" src="http://4.bp.blogspot.com/_scsqOe7UOdc/S9rrt3J83tI/AAAAAAAAAJU/wdmPsoo-va4/s400/p3.gif" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;La pantalla 4 muestra la salida del reporte de historial de los 5 eventos de espera mas importantes:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://2.bp.blogspot.com/_scsqOe7UOdc/S9rrrI65M5I/AAAAAAAAAJM/EMklIXwka3c/s1600/p4.gif" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img style="width: 400px; height: 300px; cursor: pointer;" id="BLOGGER_PHOTO_ID_5465940224128922514" alt="" src="http://2.bp.blogspot.com/_scsqOe7UOdc/S9rrrI65M5I/AAAAAAAAAJM/EMklIXwka3c/s400/p4.gif" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;La pantalla 5 muestra las sesiones actuales de la base (información actual)&lt;br /&gt;&lt;br /&gt;&lt;a href="http://2.bp.blogspot.com/_scsqOe7UOdc/S9rrn0KmHkI/AAAAAAAAAJE/2IcU0ioIox0/s1600/p5.gif" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img style="width: 400px; height: 300px; cursor: pointer;" id="BLOGGER_PHOTO_ID_5465940167018028610" alt="" src="http://2.bp.blogspot.com/_scsqOe7UOdc/S9rrn0KmHkI/AAAAAAAAAJE/2IcU0ioIox0/s400/p5.gif" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Con doble click sobre una fila de la grilla se abre otro form con detalle de la sesion seleccionada. En el panel donde aparece el texto de la sentencia que esta ejecutando la sesión se puede clickear el boton derecho y ver y seleccionar "Detalle.." para ver el historial de ejecución de dicha sentencia.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://2.bp.blogspot.com/_scsqOe7UOdc/S9rrkunz3mI/AAAAAAAAAI8/4K06LALLa-Y/s1600/p6.gif" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img style="width: 400px; height: 300px; cursor: pointer;" id="BLOGGER_PHOTO_ID_5465940113990344290" alt="" src="http://2.bp.blogspot.com/_scsqOe7UOdc/S9rrkunz3mI/AAAAAAAAAI8/4K06LALLa-Y/s400/p6.gif" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;La siguiente pantalla muestra todos los forms abiertos puestos en mosaico horizontal (menu Ventanas)&lt;br /&gt;&lt;br /&gt;&lt;a href="http://4.bp.blogspot.com/_scsqOe7UOdc/S9rrhNCfu2I/AAAAAAAAAI0/ZWcQRemoZXI/s1600/p7.gif" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img style="width: 400px; height: 300px; cursor: pointer;" id="BLOGGER_PHOTO_ID_5465940053435857762" alt="" src="http://4.bp.blogspot.com/_scsqOe7UOdc/S9rrhNCfu2I/AAAAAAAAAI0/ZWcQRemoZXI/s400/p7.gif" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;La pantalla 8 es el resultado de la ejecucion del reporte de Tablespaces&lt;br /&gt;&lt;br /&gt;&lt;a href="http://4.bp.blogspot.com/_scsqOe7UOdc/S9rrc8KYpPI/AAAAAAAAAIs/XZJVe1WNY-s/s1600/p8.gif" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img style="width: 400px; height: 300px; cursor: pointer;" id="BLOGGER_PHOTO_ID_5465939980186068210" alt="" src="http://4.bp.blogspot.com/_scsqOe7UOdc/S9rrc8KYpPI/AAAAAAAAAIs/XZJVe1WNY-s/s400/p8.gif" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Con doble click sobre la fila de la grilla se abre un nuevo form con detalle del tablespace:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://2.bp.blogspot.com/_scsqOe7UOdc/S9rrZFL5CyI/AAAAAAAAAIk/9cfMTM2Twy4/s1600/p9.gif" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img style="width: 400px; height: 300px; cursor: pointer;" id="BLOGGER_PHOTO_ID_5465939913888828194" alt="" src="http://2.bp.blogspot.com/_scsqOe7UOdc/S9rrZFL5CyI/AAAAAAAAAIk/9cfMTM2Twy4/s400/p9.gif" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;La pantalla 10 muestra los posibles filtros que se pueden realizar para analizar el historial de sentencias TOP.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://3.bp.blogspot.com/_scsqOe7UOdc/S9rrKBK-UwI/AAAAAAAAAIc/aZNucJNmY9Y/s1600/p10.gif" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img style="width: 400px; height: 300px; cursor: pointer;" id="BLOGGER_PHOTO_ID_5465939655113200386" alt="" src="http://3.bp.blogspot.com/_scsqOe7UOdc/S9rrKBK-UwI/AAAAAAAAAIc/aZNucJNmY9Y/s400/p10.gif" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;La pantalla 11 muesta las sentencias TOP que cumplen los filtros definidos anteriormente:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://3.bp.blogspot.com/_scsqOe7UOdc/S9rrHK0yJEI/AAAAAAAAAIU/bWbg_8CszRc/s1600/p11.gif" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img style="width: 400px; height: 300px; cursor: pointer;" id="BLOGGER_PHOTO_ID_5465939606164874306" alt="" src="http://3.bp.blogspot.com/_scsqOe7UOdc/S9rrHK0yJEI/AAAAAAAAAIU/bWbg_8CszRc/s400/p11.gif" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Con un click sobre la celda que tiene el SQLID se copia el valor y luego presionando el boton de REFRESCAR se "pastea" el valor copiado para ver historial de la sentencia:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://3.bp.blogspot.com/_scsqOe7UOdc/S9rrDV8RkAI/AAAAAAAAAIM/peWFRL7iD9E/s1600/p12.gif" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img style="width: 400px; height: 300px; cursor: pointer;" id="BLOGGER_PHOTO_ID_5465939540429606914" alt="" src="http://3.bp.blogspot.com/_scsqOe7UOdc/S9rrDV8RkAI/AAAAAAAAAIM/peWFRL7iD9E/s400/p12.gif" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Todas las grillas de resultado se pueden exportar a excel, se pueden filtrar y todas las consultas se puede copiar (botón derecho sobre el panel donde esta el texto de la consulta y seleccionar COPIAR).&lt;br /&gt;&lt;br /&gt;Mi idea fue mostrarles algunas pantallas para que vean la funcionalidad que intenté darle a mi aplicación. Obviamente hay varios reportes mas que no estan detallados en el blog, pero invito a quien quiera evaluar mi programita (se llama OraPerfViewer.exe y pesa alrededor de 150kb) que solo me escriba a: &lt;span style="font-weight: bold;"&gt;rovedop@gmail.com&lt;/span&gt; y les enviaré el ejecutable que solo requiere windows y un framework .NET instalado (creo que la mayoria de las windows lo traen instalado por default). Por el momento no es RAC-Aware, pero en el futuro lo será y corre sobre versiones 10g en adelante.&lt;br /&gt;&lt;br /&gt;Es una primera versión, y seguramente tenga varios bugs y cosas que mejorar, pero estoy seguro que compartiendo y discutiendo ideas se mejoran las cosas.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5727738329548735676-8765806105470090363?l=oramdq.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://oramdq.blogspot.com/feeds/8765806105470090363/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://oramdq.blogspot.com/2010/04/utilitario-casero-para-diagnosticar.html#comment-form' title='4 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/8765806105470090363'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/8765806105470090363'/><link rel='alternate' type='text/html' href='http://oramdq.blogspot.com/2010/04/utilitario-casero-para-diagnosticar.html' title='Herramienta para diagnosticar problemas de performance (Oracle Performance Viewer Freeware)'/><author><name>Pablo A. Rovedo</name><uri>http://www.blogger.com/profile/02924687287216878757</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://3.bp.blogspot.com/_scsqOe7UOdc/SYDOV9ANLfI/AAAAAAAAAFQ/71M2XO1AZ1M/S220/DSC02067+(Small).JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_scsqOe7UOdc/S9rr-lUOm3I/AAAAAAAAAJk/qSTctL4T8oE/s72-c/Pantalla_Principal.bmp' height='72' width='72'/><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5727738329548735676.post-8896533513949511559</id><published>2010-04-16T12:11:00.000-07:00</published><updated>2010-04-30T05:02:56.623-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Procesamiento'/><category scheme='http://www.blogger.com/atom/ns#' term='Performance'/><title type='text'>Hard Parse vs Soft Parse vs Non Parse</title><content type='html'>El impacto del parsing en una base de datos puede ser muy variable. En ciertos casos, afortunadamente la mayoria, no es notorio. En otros casos mas especificos puede ocasionar problemas mayores de rendimiento de la base de datos. En general los problemas de excesivo parsing se deben a una mala programación, es decir se deben analizar y solucionar desde el código de las aplicaciones que interactuan con la base. Es por eso que los desarrolladores deben estar concientes del impacto negativo que puede provocar una mala programación y considerar este tema como punto prioritario desde el inicio de la confección del código. &lt;br /&gt;&lt;br /&gt;   El parsing es el primer paso que se lleva a cabo para procesar una sentencia. En esta etapa se debe conocer de que tipo de sentencia se trata (DML, DDL o un select) para asi poder realizar los correspondientes chequeos. Los principales actividades son el chequeo sintáctico y el análisis semántico&lt;br /&gt;&lt;br /&gt;Chequeo Sintáctico&lt;br /&gt;&lt;br /&gt;Este chequeo verifica si la sentencia cumple con la gramática de la sentencia definida para la versión de la base.&lt;br /&gt;&lt;br /&gt;Análisis Semántico&lt;br /&gt;&lt;br /&gt;Se analiza si los objetos referenciados en la sentencia existen, si las columnas existen, si se tiene acceso a los segmentos y a las columnas (privilegios), etc.&lt;br /&gt;&lt;br /&gt;   Una vez que se pasan con éxito las dos etapas antes mencionadas, Oracle busca en la memoria (shared pool) para ver si ya fue ejecutada la misma sentencia por otra sesión. Si la encuentra, entonces se dice que se realizó un SOFT PARSE. Por otro lado, si no la encuentra, se realizan dos pasos adicionales, que son la optimización de la sentencia y la generación y carga del plan en la memoria (row source generation). La ejecución de todos los pasos se llama HARD PARSE. El hard parsing es cpu intensivo, y en el caso de que sea elevado puede compromenter seriamente la performance general dada la alta contención que se provoca. Para evitar el hard parsing hay que usar variables BIND en los statements (ej: usar preparedStatement). Si se trata de un código "enlatado" donde no se utilizan binds y no puede modificarse se puede utilizar en la base CURSOR_SHARING, cuyo default es EXACT y habría que cambiarlo a SIMILAR (existe a partir de 9i) o FORCE, aunque siempre recomiendo usar SIMILAR, porque es menos riesgoso. &lt;br /&gt;&lt;br /&gt;    El soft parse puede ser aún mas soft si se cachea el cursor en la sesión (session_cached_cursor) y asi se evita ir a la shared pool a buscarlo. Desde el código de la app se puede habilitar y definir el tamaño de cache mas adecuado (ej: ((oracle.jdbc.OracleConnection)connection).setStatementCacheSize(40)). Esto esta disponible en casi todas las interfaces (jdbc,.NET,PL/SQL,oci,etc).&lt;br /&gt;&lt;br /&gt;   Para evitar el reparseo en una sesión se debe mantener abierto el cursor. Algunas interfaces tales como PL/SQL, jdbc y la oci permiten realizar esto. La interfaz OLE DB, SQLJ u ODP, al menos hasta la ultima version que conozco, no lo permiten. A continuación voy a copiar 3 fragmentos de código Java para mostrar la diferencia entre parseo hard, soft y no parsear.&lt;br /&gt; &lt;br /&gt;El primer fragmento de abajo muestra la NO utilización de binding, ya que se concatenan los literales y no se usa PreparedStatement:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;TEST 1&lt;br /&gt;-------&lt;br /&gt;&lt;br /&gt;      sql = "SELECT X FROM t WHERE Y = ";&lt;br /&gt;      for (int i=0 ; i&lt;10000; i++)&lt;br /&gt;      {&lt;br /&gt;        statement = connection.createStatement();&lt;br /&gt;        resultset = statement.executeQuery(sql + Integer.toString(i));&lt;br /&gt;        if (resultset.next())&lt;br /&gt;        {&lt;br /&gt;          val = resultset.getString("X");       &lt;br /&gt;        }&lt;br /&gt;        resultset.close();&lt;br /&gt;        statement.close();&lt;br /&gt;      }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Este código, además de se muy pobre en performance, propicia el hacking por sql injection.&lt;br /&gt;&lt;br /&gt;El segundo fragmento utiliza binding pero abre y cierra el cursor en cada ejecución por lo cual genera soft parse.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;TEST 2&lt;br /&gt;-------&lt;br /&gt;&lt;br /&gt;      sql = "SELECT X FROM t WHERE Y = ?";&lt;br /&gt;      for (int i=0 ; i&lt;10000; i++)&lt;br /&gt;      {&lt;br /&gt;        statement = connection.prepareStatement(sql);&lt;br /&gt;        statement.setInt(1, i);&lt;br /&gt;        resultset = statement.executeQuery();&lt;br /&gt;        if (resultset.next())&lt;br /&gt;        {&lt;br /&gt;          val = resultset.getString("X");          &lt;br /&gt;        }&lt;br /&gt;        resultset.close();&lt;br /&gt;        statement.close();&lt;br /&gt;      } &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;El último fragmento, que es el óptimo, reduce el parsing al mínimo (solo un soft parsing):&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;TEST 3&lt;br /&gt;-------&lt;br /&gt;&lt;br /&gt;      sql = "SELECT X FROM t WHERE Y = ?";&lt;br /&gt;      statement = connection.prepareStatement(sql);&lt;br /&gt;      for (int i=0 ; i&lt;10000; i++)&lt;br /&gt;      {&lt;br /&gt;        statement.setInt(1, i);&lt;br /&gt;        resultset = statement.executeQuery();&lt;br /&gt;        if (resultset.next())&lt;br /&gt;        {&lt;br /&gt;          val = resultset.getString("X");          &lt;br /&gt;        }&lt;br /&gt;        resultset.close();&lt;br /&gt;      } &lt;br /&gt;      statement.close();&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;En una prueba que realicé los tiempos de respuesta de cada test fueron los siguientes:&lt;br /&gt;&lt;br /&gt;TEST1           --&gt; 12.2"&lt;br /&gt;TEST2           --&gt;  6.4"&lt;br /&gt;TEST2 (caching) --&gt;  3.9"&lt;br /&gt;TEST3           --&gt;  3.7"&lt;br /&gt;TEST3 (caching) --&gt;  3.7"&lt;br /&gt;&lt;br /&gt;Como se ve arriba, el TEST2, se puede mejorar usando caching, pero al usar caching en el TEST3 no se ven diferencias.&lt;br /&gt;&lt;br /&gt;El parseo se puede ver como una mini compilación, podriamos comparar un código que ejecuta en un bucle un prepareStatement para cada sentencia con un código interpretado. Cualquier programador sabrá que la ejecución de un código compilado es mucho mas rápida que ejecutar un código que necesita interpretarse linea por línea.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5727738329548735676-8896533513949511559?l=oramdq.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://oramdq.blogspot.com/feeds/8896533513949511559/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://oramdq.blogspot.com/2010/04/hard-parse-vs-soft-parse-vs-non-parse.html#comment-form' title='1 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/8896533513949511559'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/8896533513949511559'/><link rel='alternate' type='text/html' href='http://oramdq.blogspot.com/2010/04/hard-parse-vs-soft-parse-vs-non-parse.html' title='Hard Parse vs Soft Parse vs Non Parse'/><author><name>Pablo A. Rovedo</name><uri>http://www.blogger.com/profile/02924687287216878757</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://3.bp.blogspot.com/_scsqOe7UOdc/SYDOV9ANLfI/AAAAAAAAAFQ/71M2XO1AZ1M/S220/DSC02067+(Small).JPG'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5727738329548735676.post-263553784696745026</id><published>2010-03-26T07:11:00.000-07:00</published><updated>2010-03-26T08:08:34.630-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='SQL'/><category scheme='http://www.blogger.com/atom/ns#' term='Performance'/><title type='text'>Reporte de Perfil de Carga de Trabajo Historica  en la Base (Historical Load Profile)</title><content type='html'>La sentencia que copié más abajo permite realizar un reporte historico de la actividad general o carga de trabajo de una base de datos Oracle. Ya que la consulta obtiene información del AWR la cantidad de historia disponible dependerá de la retención definida en el repositorio (por default es de 7 dias aunque yo siempre aconsejo cambiarlo a 30 dias). La información proporcionada es lo que se muestra en un reporte AWR en la sección LOAD PROFILE, que esta al principio del reporte. Todos aquellos que hayan analizado performance mirando reportes de awr (ejecutando el script awrrpt.sql o bien usando una herramienta gráfica como el TOAD), sabrán que la sección de "Load Profile" junto con los 5 eventos tops son la primera "foto" del "estado de salud" general de la base. Sin embargo esa "foto" no sirve de mucho si no se conoce bien de antemano la actividad y tipo de carga o si no se ve alguna métrica con valores notoriamente grandes. &lt;br /&gt;&lt;br /&gt;Para poder realizar un análisis efectivo hay que sacar varios reportes awr y compararlos para establecer diferencias. En esos casos, yo prefiero tener toda la historia disponible de un patallazo para así comparar mas facilmente, ver si alguna métrica esta en valores no habituales, poder exportar la salida del query a una planilla y realizar un gráfico historico para confeccionar un informe. &lt;br /&gt;&lt;br /&gt;Antes de ejecutar el query es importante aclarar que los valores reportados están en unidades por segundo (primera columna del load profile de awr) y que funciona en bases 10g o superiores.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;with intervals&lt;br /&gt;as&lt;br /&gt;  (select snap,&lt;br /&gt;         extract(second from int)+&lt;br /&gt;         extract(minute from int)*60+&lt;br /&gt;         extract(hour from int)*60*60 int_sec         &lt;br /&gt;  from&lt;br /&gt;  (select end_interval_time snap,&lt;br /&gt;          end_interval_time-lead(end_interval_time) &lt;br /&gt;  over (partition by startup_time order by snap_id desc) int&lt;br /&gt;   from dba_hist_snapshot))&lt;br /&gt;select to_char(snap_time,'YYYY/MM/DD HH24') snap_time,&lt;br /&gt;       round((redo_size-lead(redo_size) &lt;br /&gt;  over (partition by startup_time order by snap_time desc))/&lt;br /&gt;       int.int_sec,2) "Redo Size",&lt;br /&gt;       round((logical_reads-lead(logical_reads) &lt;br /&gt;  over (partition by startup_time order by snap_time desc))/&lt;br /&gt;       int.int_sec,2) "Logical Reads",&lt;br /&gt;       round((db_block_changes-lead(db_block_changes) &lt;br /&gt;  over (partition by startup_time order by snap_time desc))/&lt;br /&gt;       int.int_sec,2) "Block Changes",&lt;br /&gt;       round((physical_reads-lead(physical_reads) &lt;br /&gt;  over (partition by startup_time order by snap_time desc))/&lt;br /&gt;       int.int_sec,2) "Physical Reads",&lt;br /&gt;       round((physical_writes-lead(physical_writes) &lt;br /&gt;  over (partition by startup_time order by snap_time desc))/&lt;br /&gt;       int.int_sec,2) "Physical Writes",&lt;br /&gt;       round((user_calls-lead(user_calls) &lt;br /&gt;  over (partition by startup_time order by snap_time desc))/&lt;br /&gt;       int.int_sec,2) "User Calls",&lt;br /&gt;       round((parses-lead(parses) &lt;br /&gt;  over (partition by startup_time order by snap_time desc))/&lt;br /&gt;       int.int_sec,2) "Parses",&lt;br /&gt;       round((parses_hard-lead(parses_hard) &lt;br /&gt;       over (partition by startup_time order by snap_time desc))/&lt;br /&gt;       int.int_sec,2) "Parses Hard",&lt;br /&gt;       round((sorts-lead(sorts) &lt;br /&gt;       over (partition by startup_time order by snap_time desc))/&lt;br /&gt;       int.int_sec,2) "Sorts",&lt;br /&gt;       round((logons-lead(logons) &lt;br /&gt;       over (partition by startup_time order by snap_time desc))/&lt;br /&gt;       int.int_sec,2) "Logons",&lt;br /&gt;       round((executes-lead(executes) &lt;br /&gt;       over (partition by startup_time order by snap_time desc))/&lt;br /&gt;       int.int_sec,2) "Exectutes",&lt;br /&gt;       round((user_rollbacks-lead(user_rollbacks) &lt;br /&gt;       over (partition by startup_time order by snap_time desc)+&lt;br /&gt;       user_commits-lead(user_commits) &lt;br /&gt;       over (partition by startup_time order by snap_time desc))/&lt;br /&gt;       int.int_sec,2) "Transactions"&lt;br /&gt;from&lt;br /&gt;(select s.end_interval_time snap_time,&lt;br /&gt;        s.startup_time startup_time,&lt;br /&gt;       max(decode(ss.stat_name,'redo size',value,null)) redo_size, &lt;br /&gt;       max(decode(ss.stat_name,'user rollbacks',value,null)) user_rollbacks,&lt;br /&gt;       max(decode(ss.stat_name,'user commits',value,null)) user_commits,&lt;br /&gt;       max(decode(ss.stat_name,'session logical reads',value,null)) logical_reads,&lt;br /&gt;       max(decode(ss.stat_name,'db block changes',value,null)) db_block_changes,&lt;br /&gt;       max(decode(ss.stat_name,'physical reads',value,null)) physical_reads,&lt;br /&gt;       max(decode(ss.stat_name,'physical writes',value,null)) physical_writes,&lt;br /&gt;       max(decode(ss.stat_name,'user calls',value,null)) user_calls,&lt;br /&gt;       max(decode(ss.stat_name,'parse count (total)',value,null)) parses,&lt;br /&gt;       max(decode(ss.stat_name,'parse count (hard)',value,null)) parses_hard,&lt;br /&gt;       max(decode(ss.stat_name,'sorts (memory)',value,null)) sorts,&lt;br /&gt;       max(decode(ss.stat_name,'logons cumulative',value,null)) logons,&lt;br /&gt;       max(decode(ss.stat_name,'execute count',value,null)) executes&lt;br /&gt;from dba_hist_sysstat ss,&lt;br /&gt;     dba_hist_snapshot s&lt;br /&gt;where s.snap_id = ss.snap_id&lt;br /&gt;  and ss.stat_name in ('user rollbacks','user commits','session logical reads',&lt;br /&gt;'db block changes','physical reads','physical writes','user calls',&lt;br /&gt;'parse count (total)','parse count (hard)','sorts (memory)','logons cumulative',&lt;br /&gt;'execute count','redo size')&lt;br /&gt;group by s.end_interval_time,s.startup_time) t,&lt;br /&gt;intervals int&lt;br /&gt;where t.snap_time = int.snap&lt;br /&gt;order by snap_time desc&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5727738329548735676-263553784696745026?l=oramdq.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://oramdq.blogspot.com/feeds/263553784696745026/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://oramdq.blogspot.com/2010/03/reporte-de-perfil-de-carga-de-trabajo.html#comment-form' title='0 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/263553784696745026'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/263553784696745026'/><link rel='alternate' type='text/html' href='http://oramdq.blogspot.com/2010/03/reporte-de-perfil-de-carga-de-trabajo.html' title='Reporte de Perfil de Carga de Trabajo Historica  en la Base (Historical Load Profile)'/><author><name>Pablo A. Rovedo</name><uri>http://www.blogger.com/profile/02924687287216878757</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://3.bp.blogspot.com/_scsqOe7UOdc/SYDOV9ANLfI/AAAAAAAAAFQ/71M2XO1AZ1M/S220/DSC02067+(Small).JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5727738329548735676.post-9146892939853176788</id><published>2010-03-08T12:04:00.001-08:00</published><updated>2010-03-09T04:25:00.975-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Conceptos'/><category scheme='http://www.blogger.com/atom/ns#' term='Procesamiento'/><category scheme='http://www.blogger.com/atom/ns#' term='DataWarehousing'/><title type='text'>Como solucionar errores de UNDO cuando se refrescan Vistas Materializadas</title><content type='html'>La semana pasada estuve en una reunión para definir como solucionar un inconveniente en una de las bases de un cliente. El problema estaba relacionado con el refresco de dos vistas materializadas (las voy a llamar MV1 y MV2 para mantener la privacidad) y lo que ocurría era que en los ultimos dias no se habia podido refrescar las vistas porque se cancelaba el proceso por falta de espacio de UNDO. Las vistas se refrescan en modo COMPLETE cada 1 hora mediante un job en la base y mantienen un detalle diario. En general nunca superan los 100,000 registros, pero ahora tenian mas de 100 millones ya que se detectó que por un error de filtro en el where de la vista MV1 (la MV2 usa una sentencia que referencia a MV1) se tomo el detalle de mas de 2 años en lugar de lo del día.&lt;br /&gt;&lt;br /&gt; El equipo de base de datos planteó recrear las vistas, lo cual es una solución valida y estuve de acuerdo en una primera instancia, pero tiene ciertas desventajas: 1) hay que ejecutar un drop e inmediatamente un create de cada vista lo cual puede ocasionar invalidaciones en cascada y por lo tanto debe hacerse en una ventana de mantenimiento y 2) hasta que no finalice la recreación de ambas vistas los objetos dependientes quedarán invalidos y es un tanto complicado estimar con certeza cuanto va a demorar este proceso, con el consiguiente riesgo de salirse de la ventana.&lt;br /&gt;         &lt;br /&gt;Como solución alternativa sugerí realizar un refresco de la siguiente forma (es importante notar que esto no requiere dropear ninguna mv):&lt;br /&gt;&lt;br /&gt;          sqlplus&gt;exec dbms_refresh(list=&gt;'MV1',atomic_refresh=&gt;FALSE)&lt;br /&gt;&lt;br /&gt;          sqlplus&gt;exec dbms_refresh(list=&gt;'MV2',atomic_refresh=&gt;FALSE)&lt;br /&gt;&lt;br /&gt;          A partitr de 10g el parámetro atomic_refresh por default es TRUE y para saber que significa voy a explicar brevemente como es el proceso de refresco intenamente:&lt;br /&gt;&lt;br /&gt;Cada vez que se refresca una vista en modo FORCE se ejecutan dos pasos:&lt;br /&gt;&lt;br /&gt;1) Se purga o se eliminan todas las filas actuales de la vista materializada&lt;br /&gt;2) Se insertan las nuevas filas ejecutando el query definido en la MV.&lt;br /&gt;&lt;br /&gt;       El parámetro atomic_refresh define el método que se usará para realizar el paso 1. En 10g el paso 1 implica un DELETE de todas las filas, se dice que el proceso de refresco en 10g es atómico porque el delete e insert se hacen en una sola transacción (atomicamente). Antes de 10g el valor default del parámetro era FALSE lo cual implicaba que el paso 1 se hiciera con un TRUNCATE, que obviamente es mas rapido que el DELETE ya que no es transaccional. Justamente al no ser transaccional no consume espacio en UNDO, recordar que el DELETE es la operación DML que mas undo consume por lejos, ya que se debe guardar todas las columnas de cada fila por si es necesario una vuelta atrás.&lt;br /&gt;&lt;br /&gt;       Como conté mas arriba, en el caso particular del refresco de las dos MV's, ambas, por un errror de filtrado en la MV1, quedaron con millones de filas  en lugar de con algunas pocas decenas de miles como debiera y dado que la base es 10g esta tomando el parametro default atomic_refesh = TRUE lo que dicta realizar un delete, en este caso será un delete de alrededor de 100M de filas en ambos casos y por lo tanto cancelaba siempre por espacio de UNDO, ya que no esta preparado ni cofigurado para soportar semejante borrado masivo. La sugerencia de cambiar el parametro default atomic_refresh= FALSE realizará un TRUNCATE y luego el insert refrescando las vistas en forma rapida sin necesidad de recrearlas. &lt;br /&gt;&lt;br /&gt;       Es común que una vez explicado el nuevo funcionamiento en 10g, que alguien se pregunte porque no se sigue truncando en lugar de hacer delete. La explicación es que en el caso que al realizarse el truncate y luego fallar el insert, la MV quedará vacia lo cual podría afectar el negocio ya que quedaran vacias hasta que el refresco se pueda completar con exito. En otro caso que tiene sentido el delete es cuando no pueden quedar nunca vacias las MV's porque se consultan mucho y si se hace truncate no se retornaran filas hasta que finalice el refresco. Generalmente los errores de refresco se produce cuando los datos se obtienen accediendo las tablas fuente por un dblink desde otra base. En el caso de la base en cuestión, este problema no existe ya que las MV's se refrescan con datos de tablas que estan en el mismo esquema.&lt;br /&gt;&lt;br /&gt;       Como ultima aclaración, es importante resaltar que no existe riesgo en la realización del refresco sugerido y se podrá realizar en cualquier momento del dia sin afectar el funcionamiento general. Una vez que este refrescado se podrán activar los jobs que disparan los refrescos normalmente.&lt;br /&gt;&lt;br /&gt;A continuación voy a mostrarles un ejemplo para comparar tiempos, generando una tabla T y una vista materializada MV_T&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA10G&gt; alter table t add primary key (x);&lt;br /&gt;&lt;br /&gt;Tabla modificada.&lt;br /&gt;&lt;br /&gt;rop@DESA10G&gt; create materialized view mv_t&lt;br /&gt;  2          refresh complete&lt;br /&gt;  3          as&lt;br /&gt;  4          select * from t;&lt;br /&gt;&lt;br /&gt;Vista materializada creada.&lt;br /&gt;&lt;br /&gt;rop@DESA10G&gt; set timing on&lt;br /&gt;rop@DESA10G&gt; exec dbms_mview.refresh(list=&gt;'MV_T',atomic_refresh=&gt;TRUE)&lt;br /&gt;&lt;br /&gt;Procedimiento PL/SQL terminado correctamente.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Transcurrido: 00:02:39.75&lt;/span&gt;&lt;br /&gt;rop@DESA10G&gt; exec dbms_mview.refresh(list=&gt;'MV_T',atomic_refresh=&gt;FALSE)&lt;br /&gt;&lt;br /&gt;Procedimiento PL/SQL terminado correctamente.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Transcurrido: 00:00:15.54&lt;/span&gt;&lt;br /&gt;rop@DESA10G&gt; &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;En sintesis, es importante analizar los requerimientos de negocio, si estos requerimientos soportan la corta indisponibilidad que se provoca al refrescar no atomicamente (truncate) además de la posibilidad que quede vacia la MV, producto de un error o cancelación, hasta el próximo refresh, entonces es posible refrescar mas rapido y con muy poco consumo de UNDO seteando el parámetro atomic_refresh en FALSE.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5727738329548735676-9146892939853176788?l=oramdq.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://oramdq.blogspot.com/feeds/9146892939853176788/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://oramdq.blogspot.com/2010/03/como-solucionar-errores-de-undo-cuando.html#comment-form' title='0 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/9146892939853176788'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/9146892939853176788'/><link rel='alternate' type='text/html' href='http://oramdq.blogspot.com/2010/03/como-solucionar-errores-de-undo-cuando.html' title='Como solucionar errores de UNDO cuando se refrescan Vistas Materializadas'/><author><name>Pablo A. Rovedo</name><uri>http://www.blogger.com/profile/02924687287216878757</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://3.bp.blogspot.com/_scsqOe7UOdc/SYDOV9ANLfI/AAAAAAAAAFQ/71M2XO1AZ1M/S220/DSC02067+(Small).JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5727738329548735676.post-8528134201297368185</id><published>2010-02-26T04:29:00.000-08:00</published><updated>2010-02-26T05:36:42.057-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Monitoreo'/><category scheme='http://www.blogger.com/atom/ns#' term='Diagnóstico'/><category scheme='http://www.blogger.com/atom/ns#' term='Scripts'/><title type='text'>Reporte para obtener métricas de SO desde AWR</title><content type='html'>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:&lt;br /&gt;&lt;br /&gt;STAT_NAME&lt;br /&gt;-------------------------------------------&lt;br /&gt;BUSY_TIME&lt;br /&gt;AVG_IDLE_TIME&lt;br /&gt;NUM_CPUS&lt;br /&gt;AVG_BUSY_TIME&lt;br /&gt;OS_CPU_WAIT_TIME&lt;br /&gt;VM_IN_BYTES&lt;br /&gt;AVG_USER_TIME&lt;br /&gt;AVG_SYS_TIME&lt;br /&gt;LOAD&lt;br /&gt;SYS_TIME&lt;br /&gt;RSRC_MGR_CPU_WAIT_TIME&lt;br /&gt;IDLE_TIME&lt;br /&gt;USER_TIME&lt;br /&gt;PHYSICAL_MEMORY_BYTES&lt;br /&gt;IOWAIT_TIME&lt;br /&gt;AVG_IOWAIT_TIME&lt;br /&gt;VM_OUT_BYTES&lt;br /&gt;&lt;br /&gt;y en 11g R1 se agregan las siguientes:&lt;br /&gt;&lt;br /&gt;TCP_SEND_SIZE_DEFAULT&lt;br /&gt;TCP_RECEIVE_SIZE_DEFAULT&lt;br /&gt;TCP_RECEIVE_SIZE_MAX&lt;br /&gt;NUM_CPU_SOCKETS&lt;br /&gt;TCP_SEND_SIZE_MAX&lt;br /&gt;NUM_CPU_CORES&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;set line 120&lt;br /&gt;set pagesize 9999&lt;br /&gt;set verify off&lt;br /&gt;accept horas prompt "Ingrese cantidad de horas hacia atras que desea reportar: "&lt;br /&gt;&lt;br /&gt;col snap format a20&lt;br /&gt;&lt;br /&gt;select unique to_char(snap,'DD-MON-YYYY HH24') snap,&lt;br /&gt;       avg_idle_time-lead(avg_idle_time) over (partition by st order by snap desc) avg_idle_time,&lt;br /&gt;       avg_user_time-lead(avg_user_time) over (partition by st order by snap desc) avg_user_time,&lt;br /&gt;       avg_sys_time-lead(avg_sys_time) over (partition by st order by snap desc) avg_sys_time,&lt;br /&gt;       avg_iowait_time-lead(avg_iowait_time) over (partition by st order by snap desc) avg_iowait_time,&lt;br /&gt;       os_cpu_wait_time-lead(os_cpu_wait_time) over (partition by st order by snap desc) os_cpu_wait_time&lt;br /&gt;from&lt;br /&gt;(select s.end_interval_time snap,&lt;br /&gt;        s.startup_time st,&lt;br /&gt;       max(decode(stat_name,'AVG_IDLE_TIME',value,null)) AVG_IDLE_TIME,&lt;br /&gt;       max(decode(stat_name,'AVG_USER_TIME',value,null)) AVG_USER_TIME,&lt;br /&gt;       max(decode(stat_name,'AVG_SYS_TIME',value,null)) AVG_SYS_TIME,&lt;br /&gt;       max(decode(stat_name,'AVG_IOWAIT_TIME',value,null)) AVG_IOWAIT_TIME,&lt;br /&gt;       max(decode(stat_name,'OS_CPU_WAIT_TIME',value,null)) OS_CPU_WAIT_TIME&lt;br /&gt;from dba_hist_osstat os,&lt;br /&gt;     dba_hist_snapshot s&lt;br /&gt;where s.snap_id = os.snap_id&lt;br /&gt;group by s.end_interval_time,s.startup_time)&lt;br /&gt;where snap &gt; sysdate-&amp;horas/24&lt;br /&gt;order by snap desc&lt;br /&gt;/&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;SNAP         AVG_IDLE_TIME AVG_USER_TIME AVG_SYS_TIME AVG_IOWAIT_TIME OS_CPU_WAIT_TIME&lt;br /&gt;25-FEB-10 09 211854       108399     40156  99439  2087400&lt;br /&gt;25-FEB-10 08 271995       61131     27296  97923  1199800&lt;br /&gt;25-FEB-10 07 236760       85951     31938  84592  1489500&lt;br /&gt;25-FEB-10 06 180172       130112     50168  90475  2281500&lt;br /&gt;25-FEB-10 05 182995       123247     54623  105254  2274500&lt;br /&gt;25-FEB-10 04 193166       116204     51902  127028  2197000&lt;br /&gt;25-FEB-10 03 195871       119849     45241  130806  2126400&lt;br /&gt;25-FEB-10 02 236610       85916     37782  160617  1574500&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;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.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5727738329548735676-8528134201297368185?l=oramdq.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://oramdq.blogspot.com/feeds/8528134201297368185/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://oramdq.blogspot.com/2010/02/reporte-para-obtener-metricas-de-so.html#comment-form' title='0 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/8528134201297368185'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/8528134201297368185'/><link rel='alternate' type='text/html' href='http://oramdq.blogspot.com/2010/02/reporte-para-obtener-metricas-de-so.html' title='Reporte para obtener métricas de SO desde AWR'/><author><name>Pablo A. Rovedo</name><uri>http://www.blogger.com/profile/02924687287216878757</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://3.bp.blogspot.com/_scsqOe7UOdc/SYDOV9ANLfI/AAAAAAAAAFQ/71M2XO1AZ1M/S220/DSC02067+(Small).JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5727738329548735676.post-2320156748008171519</id><published>2010-02-19T08:14:00.001-08:00</published><updated>2010-02-26T07:38:27.004-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='11g New Features'/><category scheme='http://www.blogger.com/atom/ns#' term='CBO'/><category scheme='http://www.blogger.com/atom/ns#' term='Performance'/><title type='text'>Mejorando la performance con binding con los nuevos Cursores Adaptables  (Adaptive Cursors)</title><content type='html'>Todo aquel que haya asistido a un curso de sql o haya leido libros o documentación relacionada con "binding de variables" ya sabrá que es un concepto muy vinculado con el rendimiento de aplicaciones. En especial en los sistemas OLTP se recomienda fuertemente utilizar variables bind en las sentencias para evitar el hard parsing y asi minimizar la utilización de shared pool y obtener una mejor performance al saltear la etapa de parseo en la ejecuciones sucesivas a la primera ejecución de una sentencia dada. &lt;br /&gt;&lt;br /&gt;  El hecho de "bindear" si bien esta circunscripto dentro de las buenas prácticas tambien tiene ciertos problemas ya que no se conoce que valores se usaran para instanciar las variables bind y no queda claro para el optimizador que plan armar. A partir de 9i existe un mecanismo denominado "bind peeking" que permite al optimizador conocer los valores de la primera instanciación y por lo tanto armar un plan concreto. Esta nueva caracteristica introdujo nuevos problemas. El binding y los histogramas no se llevan del todo bien. Recordemos que los histogramas ayudan al optimizador ya que le proveen de la distribución de los datos. &lt;br /&gt;&lt;br /&gt; Como dije antes, al bindear el optimizador no conoce de antemano con que valor se instanciará cada variable bind y por lo tanto no permite adecuar el plan a los valores de entrada. Si la distribución de los datos es uniforme esto no es un problema, pero que pasa si la distribución es dispar?. Que sucede si en la primera instanciación se genera un plan para usar full scan debido a que el valor de entrada tiene baja selectividad y luego las siguientes instanciaciones de valores tienen alta selectividad?. Estos ultimos generalmente requieren acceso por indices, pero el plan quedó fijado con la primera instanciación y por lo tanto usará full scan cuando en realidad debió usar acceso por indice, imaginense lo complicado que puede resultar esto. Por ejemplo, una mañana un programador ejecuta una consulta que instancia con un valor de borde o poco común para hacer un reporte complejo que recorre un porcentaje alto de filas y queda armado un plan con acceso full scan sobre una tabla grande, luego, si el cursor sigue en memoria, las aplicaciones usarán el mismo cursor para busquedas puntuales y usaran el plan generado por la consulta extraña (que usó full scan), suena caótico, no?. &lt;br /&gt;&lt;br /&gt; En 11g R1 se agregó una nueva caracteristicas llamada "adaptive cursors" que soluciona el problema de "bind peeking". A continuación les muestro unas pruebas que realicé:  &lt;br /&gt;&lt;br /&gt;Para el test voy a crear una tabla sencilla con dos columnas X e Y. La columna Y tiene 3 posibles valores (A,B y C). Donde A tiene muy baja selectividad, B tiene selectividad media y C tiene selectividad alta.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA11G&gt; create table t (x int, y char(1))&lt;br /&gt;  2  pctfree 90;&lt;br /&gt;Tabla creada.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Cree la tabla T con pctfree en 90% para que se generen muchos bloques con no tantas filas.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA11G&gt; insert into t &lt;br /&gt;  2  select t_seq.nextval,&lt;br /&gt;  3         case when (rownum between 1 and 4000000) then 'A'&lt;br /&gt;  4              when (rownum between 4000001 and 5000000) then 'B'&lt;br /&gt;  5              when (rownum between 5000001 and 5000010) then 'C'&lt;br /&gt;  6         end&lt;br /&gt;  7  from dual&lt;br /&gt;  8  connect by rownum &lt;= 5000010;&lt;br /&gt;&lt;br /&gt;5000010 filas creadas.&lt;br /&gt;&lt;br /&gt;rop@DESA11G&gt; select bytes,blocks from user_segments where segment_name = 'T';&lt;br /&gt;&lt;br /&gt;     BYTES     BLOCKS&lt;br /&gt;---------- ----------&lt;br /&gt; 679477248      82944&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Generé una tabla que pesa mas de 600Mb.&lt;br /&gt;Ahora creo un indice, recolecto estaditicas&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA11G&gt; create index t_idx on t(y);&lt;br /&gt;&lt;br /&gt;Índice creado.&lt;br /&gt;&lt;br /&gt;rop@DESA11G&gt;   begin&lt;br /&gt;  2        dbms_stats.gather_table_stats(ownname =&gt; user,&lt;br /&gt;  3                                      tabname =&gt; 'T',&lt;br /&gt;  4                                      method_opt =&gt; 'for all indexed columns',&lt;br /&gt;  5                                      cascade =&gt; true) ;&lt;br /&gt;  6    end;&lt;br /&gt;  7  /&lt;br /&gt;&lt;br /&gt;Procedimiento PL/SQL terminado correctamente.&lt;br /&gt;&lt;br /&gt;rop@DESA11G&gt; select y,count(1)&lt;br /&gt;  2          from t&lt;br /&gt;  3          group by y;&lt;br /&gt;&lt;br /&gt;Y   COUNT(1)&lt;br /&gt;- ----------&lt;br /&gt;A    4000000&lt;br /&gt;B    1000000&lt;br /&gt;C         10&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;En la ultima consulta se ve la distribución de la columna Y en la tabla T.&lt;br /&gt;&lt;br /&gt;Voy a ejecutar una consulta y la voy a instanciar la variable bind :v con el valor 'A' para que se arme un plan que utilice full_scan:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA11G&gt; variable v char(1);&lt;br /&gt;rop@DESA11G&gt; exec :v:= 'A';&lt;br /&gt;&lt;br /&gt;Procedimiento PL/SQL terminado correctamente.&lt;br /&gt;&lt;br /&gt;rop@DESA11G&gt; set autotr on&lt;br /&gt;rop@DESA11G&gt; select avg(x) from t where y = :v;&lt;br /&gt;&lt;br /&gt;    AVG(X)&lt;br /&gt;----------&lt;br /&gt;21640320.5&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Plan de Ejecución&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;Plan hash value: 1842905362&lt;br /&gt;&lt;br /&gt;---------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |&lt;br /&gt;---------------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT   |      |     1 |     7 | 22533   (1)| 00:04:31 |&lt;br /&gt;|   1 |  SORT AGGREGATE    |      |     1 |     7 |            |          |&lt;br /&gt;|*  2 |   TABLE ACCESS FULL| T    |  1666K|    11M| 22533   (1)| 00:04:31 |&lt;br /&gt;---------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;   2 - filter("Y"=:V)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Estadísticas&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;        329  recursive calls&lt;br /&gt;          0  db block gets&lt;br /&gt;      &lt;span style="font-weight:bold;"&gt;82086  consistent gets&lt;/span&gt;&lt;br /&gt;      &lt;span style="font-weight:bold;"&gt;82045  physical reads&lt;/span&gt;&lt;br /&gt;          0  redo size&lt;br /&gt;        243  bytes sent via SQL*Net to client&lt;br /&gt;        233  bytes received via SQL*Net from client&lt;br /&gt;          2  SQL*Net roundtrips to/from client&lt;br /&gt;          7  sorts (memory)&lt;br /&gt;          0  sorts (disk)&lt;br /&gt;          1  rows processed&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;El plan usó efectivamente full_scan. Notar la gran cantidad de lecturas fisicas.&lt;br /&gt;&lt;br /&gt;Consultemos la vista V$SQL:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA11G&gt; select sql_id,child_number,is_bind_sensitive, is_bind_aware&lt;br /&gt;  2  from v$sql&lt;br /&gt;  3  where sql_text = 'select avg(x) from t where y = :v';&lt;br /&gt;&lt;br /&gt;SQL_ID        CHILD_NUMBER IS_BIND_SENSITIVE    IS_BIND_AWARE&lt;br /&gt;------------- ------------ -------------------- ---------------&lt;br /&gt;d9p5ax32fmqdn            0 Y                    N&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Como se observa, en 11g se agregaron nuevas columnas a la vista v$sql relativas a las variables bind. &lt;br /&gt;&lt;br /&gt;Instanciemos Y := 'C', que tiene muy alta selectividad:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA11G&gt; exec :v:='C';&lt;br /&gt;&lt;br /&gt;Procedimiento PL/SQL terminado correctamente.&lt;br /&gt;&lt;br /&gt;rop@DESA11G&gt; set autotr on&lt;br /&gt;rop@DESA11G&gt; select avg(x) from t where y = :v;&lt;br /&gt;&lt;br /&gt;    AVG(X)&lt;br /&gt;----------&lt;br /&gt;24640325.5&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Plan de Ejecución&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;Plan hash value: 1842905362&lt;br /&gt;&lt;br /&gt;---------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |&lt;br /&gt;---------------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT   |      |     1 |     7 | 22533   (1)| 00:04:31 |&lt;br /&gt;|   1 |  SORT AGGREGATE    |      |     1 |     7 |            |          |&lt;br /&gt;|*  2 |   TABLE ACCESS FULL| T    |  1666K|    11M| 22533   (1)| 00:04:31 |&lt;br /&gt;---------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;   2 - filter("Y"=:V)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Estadísticas&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;          0  recursive calls&lt;br /&gt;          0  db block gets&lt;br /&gt;     &lt;span style="font-weight:bold;"&gt; 82037  consistent gets&lt;/span&gt;&lt;br /&gt;     &lt;span style="font-weight:bold;"&gt; 82025  physical reads&lt;/span&gt;&lt;br /&gt;          0  redo size&lt;br /&gt;        243  bytes sent via SQL*Net to client&lt;br /&gt;        233  bytes received via SQL*Net from client&lt;br /&gt;          2  SQL*Net roundtrips to/from client&lt;br /&gt;          0  sorts (memory)&lt;br /&gt;          0  sorts (disk)&lt;br /&gt;          1  rows processed&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Usó full scan, para retornar solo 10 valores, observemos nuevamente las lecturas fisicas.&lt;br /&gt;&lt;br /&gt;Voy a consultar nuevamente la vista v$sql:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA11G&gt; select sql_id,child_number,is_bind_sensitive, is_bind_aware&lt;br /&gt;  2  from v$sql&lt;br /&gt;  3  where sql_text = 'select avg(x) from t where y = :v';&lt;br /&gt;&lt;br /&gt;SQL_ID        CHILD_NUMBER IS_BIND_SENSITIVE    IS_BIND_AWARE&lt;br /&gt;------------- ------------ -------------------- ---------------&lt;br /&gt;d9p5ax32fmqdn            0 Y                    N&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Sigue igual que antes.&lt;br /&gt;Vuelvo a repetir la consulta anterior:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA11G&gt; select avg(x) from t where y = :v;&lt;br /&gt;&lt;br /&gt;    AVG(X)&lt;br /&gt;----------&lt;br /&gt;24640325.5&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Plan de Ejecución&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;Plan hash value: 1842905362&lt;br /&gt;&lt;br /&gt;---------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |&lt;br /&gt;---------------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT   |      |     1 |     7 | 22533   (1)| 00:04:31 |&lt;br /&gt;|   1 |  SORT AGGREGATE    |      |     1 |     7 |            |          |&lt;br /&gt;|*  2 |   TABLE ACCESS FULL| T    |  1666K|    11M| 22533   (1)| 00:04:31 |&lt;br /&gt;---------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;   2 - filter("Y"=:V)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Estadísticas&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;          1  recursive calls&lt;br /&gt;          0  db block gets&lt;br /&gt;          &lt;span style="font-weight:bold;"&gt;4  consistent gets&lt;/span&gt;&lt;br /&gt;         &lt;span style="font-weight:bold;"&gt; 4  physical reads&lt;/span&gt;&lt;br /&gt;          0  redo size&lt;br /&gt;        243  bytes sent via SQL*Net to client&lt;br /&gt;        233  bytes received via SQL*Net from client&lt;br /&gt;          2  SQL*Net roundtrips to/from client&lt;br /&gt;          0  sorts (memory)&lt;br /&gt;          0  sorts (disk)&lt;br /&gt;          1  rows processed&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;El plan sigue marcando acceso full scan, pero observemos ahora las lecturas fisicas. Fueron solo 4!.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA11G&gt; set autotr off&lt;br /&gt;rop@DESA11G&gt; select sql_id,child_number,is_bind_sensitive, is_bind_aware&lt;br /&gt;  2  from v$sql&lt;br /&gt;  3  where sql_text = 'select avg(x) from t where y = :v';&lt;br /&gt;&lt;br /&gt;SQL_ID        CHILD_NUMBER IS_BIND_SENSITIVE    IS_BIND_AWARE&lt;br /&gt;------------- ------------ -------------------- ---------------&lt;br /&gt;d9p5ax32fmqdn            0 Y                    N&lt;br /&gt;d9p5ax32fmqdn            1 Y                    Y&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;La consulta ahora muestra otro sqlid que es hijo del original con la columna is_bind_aware en "Y". El último plan mostró full scan aunque no concuerda con las pocas lecturas físicas y lógicas, ya que el método que usé para obtener el plan (dbms_xplan.diplay no tiene la opción de indicar el child) no muestra el plan del child 1 sino solo el plan padre (child=0). Después voy a mostrar con un trace 10046 que efectivamente usó acceso por indice (tambien se puede hacer con dbms_xplan.display_cursor indicando el child), lo cual cierra con la poca cantidad de lecturas que necesitó.&lt;br /&gt;&lt;br /&gt;Consultando las nuevas vistas de "adaptive cursors" se ve como Oracle lleva registro de las ejecuciones y se adapta automaticamente a los cambios abruptos de selectividad al instanciar las variables:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA11G&gt; select  child_number, &lt;br /&gt;  2  bind_set_hash_value, &lt;br /&gt;  3  peeked, &lt;br /&gt;  4  executions, &lt;br /&gt;  5  rows_processed, &lt;br /&gt;  6  buffer_gets, &lt;br /&gt;  7  cpu_time&lt;br /&gt;  8  from v$sql_cs_statistics&lt;br /&gt;  9  where sql_id ='d9p5ax32fmqdn';&lt;br /&gt;&lt;br /&gt;CHILD_NUMBER BIND_SET_HASH_VALUE P EXECUTIONS ROWS_PROCESSED BUFFER_GETS   CPU_TIME&lt;br /&gt;------------ ------------------- - ---------- -------------- ----------- ----------&lt;br /&gt;           1          2477564004 Y          1             21           4          0&lt;br /&gt;           0           816821622 Y          1        4000001       82086          0&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;rop@DESA11G&gt; ed&lt;br /&gt;Escrito file afiedt.buf&lt;br /&gt;&lt;br /&gt;  1  select * from v$sql_cs_histogram&lt;br /&gt;  2  where sql_id ='d9p5ax32fmqdn'&lt;br /&gt;  3* order by child_number,bucket_id&lt;br /&gt;rop@DESA11G&gt; /&lt;br /&gt;&lt;br /&gt;ADDRESS          HASH_VALUE SQL_ID        CHILD_NUMBER  BUCKET_ID      COUNT&lt;br /&gt;---------------- ---------- ------------- ------------ ---------- ----------&lt;br /&gt;000000044B9DC7B8 3303659956 d9p5ax32fmqdn            0          0          1&lt;br /&gt;000000044B9DC7B8 3303659956 d9p5ax32fmqdn            0          1          0&lt;br /&gt;000000044B9DC7B8 3303659956 d9p5ax32fmqdn            0          2          1&lt;br /&gt;000000044B9DC7B8 3303659956 d9p5ax32fmqdn            1          0          3&lt;br /&gt;000000044B9DC7B8 3303659956 d9p5ax32fmqdn            1          1          0&lt;br /&gt;000000044B9DC7B8 3303659956 d9p5ax32fmqdn            1          2          0&lt;br /&gt;&lt;br /&gt;6 filas seleccionadas.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Registrando la actividad y midiendo internamente rapidamente se detectó que el plan no era adecuado y se cambió.&lt;br /&gt;&lt;br /&gt;A continuación muestro el resultado de tracear con el evento 10046:&lt;br /&gt;&lt;br /&gt;El cursor principal o padre:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;********************************************************************************&lt;br /&gt;&lt;br /&gt;SQL ID: d9p5ax32fmqdn&lt;br /&gt;Plan Hash: 1842905362&lt;br /&gt;select avg(x)&lt;br /&gt;from&lt;br /&gt; t where y = :v&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;call     count       cpu    elapsed       disk      query    current        rows&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;Parse        3      0.00       0.00          0          0          0           0&lt;br /&gt;Execute      3      0.00       0.00          0          0          0           0&lt;br /&gt;Fetch        6     12.58      11.26     246075     246111          0           3&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;total       12     12.58      11.26     246075     246111          0           3&lt;br /&gt;&lt;br /&gt;Misses in library cache during parse: 1&lt;br /&gt;Misses in library cache during execute: 1&lt;br /&gt;Optimizer mode: ALL_ROWS&lt;br /&gt;Parsing user id: 82&lt;br /&gt;&lt;br /&gt;Rows     Row Source Operation&lt;br /&gt;-------  ---------------------------------------------------&lt;br /&gt;      1  SORT AGGREGATE (cr=82037 pr=82025 pw=0 time=0 us)&lt;br /&gt;4000000   TABLE ACCESS FULL T (cr=82037 pr=82025 pw=0 time=19822 us cost=22641 size=28010066 card=4001438)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Elapsed times include waiting on following events:&lt;br /&gt;  Event waited on                             Times   Max. Wait  Total Waited&lt;br /&gt;  ----------------------------------------   Waited  ----------  ------------&lt;br /&gt;  SQL*Net message to client                       6        0.00          0.00&lt;br /&gt;  direct path read                             1956        0.23          1.58&lt;br /&gt;  SQL*Net message from client                     6        0.00          0.04&lt;br /&gt;********************************************************************************&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;El cursor hijo 1:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;********************************************************************************&lt;br /&gt;&lt;br /&gt;SQL ID: 804rjbx6snjv4&lt;br /&gt;Plan Hash: 3178687684&lt;br /&gt;select avg(x)&lt;br /&gt;from&lt;br /&gt; t where y = :v&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;call     count       cpu    elapsed       disk      query    current        rows&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;Parse        1      0.00       0.00          0          0          0           0&lt;br /&gt;Execute      1      0.00       0.00          0          0          0           0&lt;br /&gt;Fetch        2      0.00       0.00          0          4          0           1&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;total        4      0.00       0.00          0          4          0           1&lt;br /&gt;&lt;br /&gt;Misses in library cache during parse: 1&lt;br /&gt;Misses in library cache during execute: 1&lt;br /&gt;Optimizer mode: ALL_ROWS&lt;br /&gt;Parsing user id: 82&lt;br /&gt;&lt;br /&gt;Rows     Row Source Operation&lt;br /&gt;-------  ---------------------------------------------------&lt;br /&gt;      1  SORT AGGREGATE (cr=4 pr=0 pw=0 time=0 us)&lt;br /&gt;     10   TABLE ACCESS BY INDEX ROWID T (cr=4 pr=0 pw=0 time=0 us cost=4 size=7 card=1)&lt;br /&gt;     10    INDEX RANGE SCAN T_IDX (cr=3 pr=0 pw=0 time=0 us cost=3 size=0 card=1)(object id 83154)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Elapsed times include waiting on following events:&lt;br /&gt;  Event waited on                             Times   Max. Wait  Total Waited&lt;br /&gt;  ----------------------------------------   Waited  ----------  ------------&lt;br /&gt;  SQL*Net message to client                       2        0.00          0.00&lt;br /&gt;  SQL*Net message from client                     2        0.00          0.01&lt;br /&gt;********************************************************************************&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Una vez mas vemos que versión tras versión se van agregando "correcciones" al optimizador por costos para minimizar el margen de error y estabilizar los sistemas.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5727738329548735676-2320156748008171519?l=oramdq.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://oramdq.blogspot.com/feeds/2320156748008171519/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://oramdq.blogspot.com/2010/02/mejorando-la-performance-con-binding.html#comment-form' title='0 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/2320156748008171519'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/2320156748008171519'/><link rel='alternate' type='text/html' href='http://oramdq.blogspot.com/2010/02/mejorando-la-performance-con-binding.html' title='Mejorando la performance con binding con los nuevos Cursores Adaptables  (Adaptive Cursors)'/><author><name>Pablo A. Rovedo</name><uri>http://www.blogger.com/profile/02924687287216878757</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://3.bp.blogspot.com/_scsqOe7UOdc/SYDOV9ANLfI/AAAAAAAAAFQ/71M2XO1AZ1M/S220/DSC02067+(Small).JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5727738329548735676.post-8326882933522632522</id><published>2010-02-12T11:01:00.000-08:00</published><updated>2010-02-26T10:56:21.340-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Administración'/><category scheme='http://www.blogger.com/atom/ns#' term='11g New Features'/><category scheme='http://www.blogger.com/atom/ns#' term='11g R2'/><category scheme='http://www.blogger.com/atom/ns#' term='Mantenimiento'/><title type='text'>Compresión avanzada de Tablas en 11g (OLTP Compression)</title><content type='html'>En esta nota la idea es mostrarles algunas de las primeras pruebas que realicé sobre compresión de tablas. Uno de los features mas fervientemente presentados por Oracle en su última versión (11g) es justamente la compresión avanzada. Desde 9i se puede comprimir tablas pero con ciertas restricciones. En 9i y 10g la compresión de tablas la podriamos llamar básica ya que tiene varias limitaciones y la compresión solo aplica para un restringido set de operaciones (por ejemplo para el insert directo) lo que lo hace bastante util para sistemas DW pero no tanto para las bases OLTP. &lt;br /&gt;&lt;br /&gt;Oracle 11g introdujo la compresión avanzada con el fin de reducir la utilización de recursos y la manipulación de grandes volúmenes de datos. Permite una sensible reducción del storage requerido para datos relacionales o estructurados (tablas), datos no estructurados (archivos) o datos de respaldo (backups). &lt;br /&gt;&lt;br /&gt;El nuevo feature OLTP Table Compression usa un algoritmo diseñado para trabajar con aplicaciones OLTP. Dicho algoritmo trabaja eliminando valores duplicados a nivel de bloque. El ratio de compresión esperado es de 2 a 3 usando OLTP Compression. Lo más novedoso es que con este feature se puede leer la información comprimida sin necesidad de descomprimirla por lo tanto no existe degradación de rendimiento y hasta puede mejorar la performance debido a una reducción de I/O ya que se necesitará acceder menos bloques para obtener las mismas filas sumado con la consiguiente reducción de buffer cache requerido. De todas formas a mi me gusta ver para creer y por lo tanto les voy a mostrar mis pruebas para que uds saquen sus propias conclusiones, además de incentivarlos a tomar como práctica habitual testear siempre antes de implementar. &lt;br /&gt;&lt;br /&gt;No tengo a mano en estos momentos una base R2 de 11g asi que mis pruebas se van a basar en R1. En R2 cambio la sintaxis pero la semántica de las operaciones es la misma que en R1. Por si alguno quisiera probar mi test en R2, y tiene pereza de consultar el manual SQL Reference, les paso las diferencias:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;COMPRESS FOR ALL OPERATIONS         (11gR1) = COMPRESS FOR OLTP (11gR2)&lt;br /&gt;COMPRESS FOR DIRECT_LOAD OPERATIONS (11gR1) = COMPRESS BASIC (11gR2)  (*)default&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Para la prueba voy a crear 3 tablas:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;T       : Sin Compresión&lt;br /&gt;T_C_DSS : Compresión DSS o compresión básica (el mismo tipo de compresión de &lt;br /&gt;          versiones anteriores)&lt;br /&gt;T_C_OLTP: Compresión avanzada (11g+)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Conectado a:&lt;br /&gt;Oracle Database 11g Enterprise Edition Release 11.1.0.7.0 - 64bit Production&lt;br /&gt;With the Partitioning, OLAP, Data Mining and Real Application Testing options&lt;br /&gt;&lt;br /&gt;rop@ROP11G&gt; create table t (x int,y char(30),z date);&lt;br /&gt;&lt;br /&gt;Tabla creada.&lt;br /&gt;&lt;br /&gt;rop@ROP11G&gt; create table t_c_dss (x int,y char(30),z date) compress;&lt;br /&gt;&lt;br /&gt;Tabla creada.&lt;br /&gt;&lt;br /&gt;rop@ROP11G&gt; create table t_c_oltp (x int,y char(30),z date) compress for all operations;&lt;br /&gt;&lt;br /&gt;Tabla creada.&lt;br /&gt;&lt;br /&gt;rop@ROP11G&gt; select table_name,pct_free,compression,compress_for from user_tables&lt;br /&gt;  2  where table_name in ('T','T_C_DSS','T_C_OLTP');&lt;br /&gt;&lt;br /&gt;TABLE_NAME                       PCT_FREE COMPRESS COMPRESS_FOR&lt;br /&gt;------------------------------ ---------- -------- ------------------&lt;br /&gt;T                                      10 DISABLED&lt;br /&gt;T_C_DSS                                 0 ENABLED  DIRECT LOAD ONLY&lt;br /&gt;T_C_OLTP                               10 ENABLED  FOR ALL OPERATIONS&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Consultando en el catálogo se puede ver el tipo de partición. Observar que el pctfree en dss es 0 ya que no se esperan cambios.&lt;br /&gt;Ahora voy a insertarles 5M de filas en forma aleatoria pero buscando un forma de que se repitan muchas veces valores en las columnas para que se aproveche la compresión&lt;br /&gt;La columna x insertará valores unicos, la columna y insertará letras A y B y la columna z insertará fechas de hasta 10 dias posteriores al test.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@ROP11G&gt; set timing on&lt;br /&gt;rop@ROP11G&gt; set autotr on&lt;br /&gt;rop@ROP11G&gt; insert into t&lt;br /&gt;  2  select rownum ,&lt;br /&gt;  3         chr(64+trunc(dbms_random.value(1,3))),&lt;br /&gt;  4         trunc(sysdate)+trunc(dbms_random.value(1,10))&lt;br /&gt;  5  from dual&lt;br /&gt;  6  connect by rownum &lt;= 5000000;&lt;br /&gt;&lt;br /&gt;5000000 filas creadas.&lt;br /&gt;&lt;br /&gt;Transcurrido: 00:02:57.65&lt;br /&gt;&lt;br /&gt;Plan de Ejecución&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;Plan hash value: 1350848739&lt;br /&gt;&lt;br /&gt;-------------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation                      | Name | Rows  | Cost (%CPU)| Time     |&lt;br /&gt;-------------------------------------------------------------------------------&lt;br /&gt;|   0 | INSERT STATEMENT               |      |     1 |     2   (0)| 00:00:01 |&lt;br /&gt;|   1 |  LOAD TABLE CONVENTIONAL       | T    |       |            |          |&lt;br /&gt;|   2 |   COUNT                        |      |       |            |          |&lt;br /&gt;|*  3 |    CONNECT BY WITHOUT FILTERING|      |       |            |          |&lt;br /&gt;|   4 |     FAST DUAL                  |      |     1 |     2   (0)| 00:00:01 |&lt;br /&gt;-------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;   3 - filter(ROWNUM&lt;=5000000)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Estadísticas&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;       4918  recursive calls&lt;br /&gt;     304059  db block gets&lt;br /&gt;      67349  consistent gets&lt;br /&gt;          5  physical reads&lt;br /&gt;  292595868  redo size&lt;br /&gt;        875  bytes sent via SQL*Net to client&lt;br /&gt;        690  bytes received via SQL*Net from client&lt;br /&gt;          6  SQL*Net roundtrips to/from client&lt;br /&gt;          4  sorts (memory)&lt;br /&gt;          0  sorts (disk)&lt;br /&gt;    5000000  rows processed&lt;br /&gt;&lt;br /&gt;rop@ROP11G&gt; commit;&lt;br /&gt;&lt;br /&gt;Confirmación terminada.&lt;br /&gt;&lt;br /&gt;Transcurrido: 00:00:00.06&lt;br /&gt;rop@ROP11G&gt; insert into t_c_dss&lt;br /&gt;  2  select rownum ,&lt;br /&gt;  3         chr(64+trunc(dbms_random.value(1,3))),&lt;br /&gt;  4         trunc(sysdate)+trunc(dbms_random.value(1,10))&lt;br /&gt;  5  from dual&lt;br /&gt;  6  connect by rownum &lt;= 5000000;&lt;br /&gt;&lt;br /&gt;5000000 filas creadas.&lt;br /&gt;&lt;br /&gt;Transcurrido: 00:02:48.43&lt;br /&gt;&lt;br /&gt;Plan de Ejecución&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;Plan hash value: 1350848739&lt;br /&gt;&lt;br /&gt;----------------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation                      | Name    | Rows  | Cost (%CPU)| Time     |&lt;br /&gt;----------------------------------------------------------------------------------&lt;br /&gt;|   0 | INSERT STATEMENT               |         |     1 |     2   (0)| 00:00:01 |&lt;br /&gt;|   1 |  LOAD TABLE CONVENTIONAL       | T_C_DSS |       |            |          |&lt;br /&gt;|   2 |   COUNT                        |         |       |            |          |&lt;br /&gt;|*  3 |    CONNECT BY WITHOUT FILTERING|         |       |            |          |&lt;br /&gt;|   4 |     FAST DUAL                  |         |     1 |     2   (0)| 00:00:01 |&lt;br /&gt;----------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;   3 - filter(ROWNUM&lt;=5000000)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Estadísticas&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;       4644  recursive calls&lt;br /&gt;     276921  db block gets&lt;br /&gt;      60593  consistent gets&lt;br /&gt;          0  physical reads&lt;br /&gt;  290874948  redo size&lt;br /&gt;        875  bytes sent via SQL*Net to client&lt;br /&gt;        696  bytes received via SQL*Net from client&lt;br /&gt;          6  SQL*Net roundtrips to/from client&lt;br /&gt;          3  sorts (memory)&lt;br /&gt;          0  sorts (disk)&lt;br /&gt;    5000000  rows processed&lt;br /&gt;&lt;br /&gt;rop@ROP11G&gt; commit;&lt;br /&gt;&lt;br /&gt;Confirmación terminada.&lt;br /&gt;&lt;br /&gt;Transcurrido: 00:00:00.04&lt;br /&gt;&lt;br /&gt;rop@ROP11G&gt; insert into t_c_oltp&lt;br /&gt;  2  select rownum ,&lt;br /&gt;  3         chr(64+trunc(dbms_random.value(1,3))),&lt;br /&gt;  4         trunc(sysdate)+trunc(dbms_random.value(1,10))&lt;br /&gt;  5  from dual&lt;br /&gt;  6  connect by rownum &lt;= 5000000;&lt;br /&gt;&lt;br /&gt;5000000 filas creadas.&lt;br /&gt;&lt;br /&gt;Transcurrido: 00:08:58.00&lt;br /&gt;&lt;br /&gt;Plan de Ejecución&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;Plan hash value: 1350848739&lt;br /&gt;&lt;br /&gt;-----------------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation                      | Name     | Rows  | Cost (%CPU)| Time     |&lt;br /&gt;-----------------------------------------------------------------------------------&lt;br /&gt;|   0 | INSERT STATEMENT               |          |     1 |     2   (0)| 00:00:01 |&lt;br /&gt;|   1 |  LOAD TABLE CONVENTIONAL       | T_C_OLTP |       |            |          |&lt;br /&gt;|   2 |   COUNT                        |          |       |            |          |&lt;br /&gt;|*  3 |    CONNECT BY WITHOUT FILTERING|          |       |            |          |&lt;br /&gt;|   4 |     FAST DUAL                  |          |     1 |     2   (0)| 00:00:01 |&lt;br /&gt;-----------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;   3 - filter(ROWNUM&lt;=5000000)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Estadísticas&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;       7402  recursive calls&lt;br /&gt;     450484  db block gets&lt;br /&gt;      93862  consistent gets&lt;br /&gt;          3  physical reads&lt;br /&gt;  945961224  redo size&lt;br /&gt;        877  bytes sent via SQL*Net to client&lt;br /&gt;        697  bytes received via SQL*Net from client&lt;br /&gt;          6  SQL*Net roundtrips to/from client&lt;br /&gt;          3  sorts (memory)&lt;br /&gt;          0  sorts (disk)&lt;br /&gt;    5000000  rows processed&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Los tiempos de inserción convencional para cada tablas fueron:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;T        = 2m 57s&lt;br /&gt;T_C_DSS  = 2m 48s&lt;br /&gt;T_C_OLTP = 8m 54s&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;El tiempo de inserción sobre la tabla oltp fue 3 veces mayor al de la tabla dss y la tabla común, pero lo mas llamativo es la cantidad de redo usado:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Redo Insert en T        =  292595868 ~=  279Mb  &lt;br /&gt;Redo Insert en T_C_DSS  =  290874948 ~=  277Mb&lt;br /&gt;Redo Insert en T_C_OLTP =  945961224 ~=  902Mb&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;También se observa que la cantidad de redo es de mas de 3 veces.&lt;br /&gt;Busqué en metalink y la nota "&lt;span style="font-weight:bold;"&gt;COMPRESS FOR ALL OPERATIONS generates lot of redo [ID 829068.1]"&lt;/span&gt; declara que es esperable bastante más consumo de redo en las operaciones dml sobre una tabla con compresión avanzada (CA).&lt;br /&gt;&lt;br /&gt;De todas formas, tarda mas pero veamos si comprimió y cuanto:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@ROP11G&gt; select segment_name,blocks,round(bytes/1024/1024,2) Mb from user_segments&lt;br /&gt;  2  where segment_name in ('T','T_C_DSS','T_C_OLTP');&lt;br /&gt;&lt;br /&gt;SEGMENT_NAME                       BLOCKS         MB&lt;br /&gt;------------------------------ ---------- ----------&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;T                                   35456        277&lt;br /&gt;T_C_DSS                             31744        248&lt;br /&gt;T_C_OLTP                            11264         88&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;El factor de compresión sobre la table T_C_OLTP fue importante comparado con las otras dos tablas. Tambien observamos que la relación de 3 se sigue manteniendo ya que la tabla oltp aloca 3 veces menos espacio que las tablas con compresión dss y sin compresión que alocaron espacio similar. Parece que sacrificando mayor consumo de redo y más tiempo de procesamiento del insert tuvo sus resultados, no?.  &lt;br /&gt;&lt;br /&gt;La próxima prueba será analizar los inserts directos. Recordemos que este tipo de insert es muy usado para carga masiva , en especial en sistemas Datawarehouse (ETL).&lt;br /&gt;Voy a testear la inserción de 1M de filas:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@ROP11G&gt; insert /*+ APPEND */ into t&lt;br /&gt;  2  select rownum ,&lt;br /&gt;  3         chr(64+trunc(dbms_random.value(1,3))),&lt;br /&gt;  4         trunc(sysdate)+trunc(dbms_random.value(1,10))&lt;br /&gt;  5  from dual&lt;br /&gt;  6  connect by rownum &lt;= 1000000;&lt;br /&gt;&lt;br /&gt;1000000 filas creadas.&lt;br /&gt;&lt;br /&gt;Transcurrido: 00:00:29.37&lt;br /&gt;&lt;br /&gt;  1  insert /*+ APPEND */ into t_c_dss&lt;br /&gt;  2  select rownum ,&lt;br /&gt;  3         chr(64+trunc(dbms_random.value(1,3))),&lt;br /&gt;  4         trunc(sysdate)+trunc(dbms_random.value(1,10))&lt;br /&gt;  5  from dual&lt;br /&gt;  6* connect by rownum &lt;= 1000000&lt;br /&gt;rop@ROP11G&gt; /&lt;br /&gt;&lt;br /&gt;1000000 filas creadas.&lt;br /&gt;&lt;br /&gt;Transcurrido: 00:00:32.01&lt;br /&gt;rop@ROP11G&gt; commit;&lt;br /&gt;&lt;br /&gt;Confirmación terminada.&lt;br /&gt;&lt;br /&gt;Transcurrido: 00:00:00.07&lt;br /&gt;rop@ROP11G&gt; ed&lt;br /&gt;Escrito file afiedt.buf&lt;br /&gt;&lt;br /&gt;  1  insert /*+ APPEND */ into t_c_oltp&lt;br /&gt;  2  select rownum ,&lt;br /&gt;  3         chr(64+trunc(dbms_random.value(1,3))),&lt;br /&gt;  4         trunc(sysdate)+trunc(dbms_random.value(1,10))&lt;br /&gt;  5  from dual&lt;br /&gt;  6* connect by rownum &lt;= 1000000&lt;br /&gt;rop@ROP11G&gt; /&lt;br /&gt;&lt;br /&gt;1000000 filas creadas.&lt;br /&gt;&lt;br /&gt;Transcurrido: 00:00:30.01&lt;br /&gt;rop@ROP11G&gt; commit;&lt;br /&gt;&lt;br /&gt;Confirmación terminada.&lt;br /&gt;&lt;br /&gt;Transcurrido: 00:00:00.01&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Con el insert directo los tiempos fueron similares:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;T        = 29s&lt;br /&gt;T_C_DSS  = 32s&lt;br /&gt;T_C_OLTP = 30s&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;y el espacio alocado:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@ROP11G&gt; ed&lt;br /&gt;Escrito file afiedt.buf&lt;br /&gt;&lt;br /&gt;  1  select segment_name,blocks,round(bytes/1024/1024,2) Mb from user_segments&lt;br /&gt;  2* where segment_name in ('T','T_C_DSS','T_C_OLTP')&lt;br /&gt;rop@ROP11G&gt; /&lt;br /&gt;&lt;br /&gt;SEGMENT_NAME                       BLOCKS         MB&lt;br /&gt;------------------------------ ---------- ----------&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;T                                   48768        381&lt;br /&gt;T_C_DSS                             33792        264&lt;br /&gt;T_C_OLTP                            13312        104&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;De los resultados de arriba vemos que el insert directo incremento el espacio alocado de cada segmento de la siguiente manera:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SEGMENT_NAME                   deltha en Mb&lt;br /&gt;------------------------------ -------&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;T                              104 (381-277)&lt;br /&gt;T_C_DSS                        16  (264-248)&lt;br /&gt;T_C_OLTP                       16  (104-88)&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;De estos resultados se deduce que la compresión avanzada y la basica funcionaron igual para los insert directos, ambas solo tuvieron que alocar 16Mb adicionales para acomodar 1M de filas nuevas. Sin embargo la tabla si compresión tuvo que alocar un 25% extra.&lt;br /&gt;&lt;br /&gt;Otro punto que me interesa testear son los update's y ver como se comporta este tipo de operación en cada caso. Para probar esto voy a cambiar la columna en 50000 filas. &lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA11G&gt; update t set y='C' where y = 'B' and rownum &lt;= 50000;&lt;br /&gt;&lt;br /&gt;50000 filas actualizadas.&lt;br /&gt;&lt;br /&gt;Transcurrido: 00:00:01.96&lt;br /&gt;&lt;br /&gt;Plan de Ejecución&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;Plan hash value: 3603919313&lt;br /&gt;&lt;br /&gt;----------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation           | Name | Rows  | Bytes | Cost (%CPU)| Time     |&lt;br /&gt;----------------------------------------------------------------------------&lt;br /&gt;|   0 | UPDATE STATEMENT    |      | 50000 |  1562K| 11537   (3)| 00:02:19 |&lt;br /&gt;|   1 |  UPDATE             | T    |       |       |            |          |&lt;br /&gt;|*  2 |   COUNT STOPKEY     |      |       |       |            |          |&lt;br /&gt;|*  3 |    TABLE ACCESS FULL| T    |  2443K|    74M| 11537   (3)| 00:02:19 |&lt;br /&gt;----------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;   2 - filter(ROWNUM&lt;=50000)&lt;br /&gt;   3 - filter("Y"='B')&lt;br /&gt;&lt;br /&gt;Note&lt;br /&gt;-----&lt;br /&gt;   - dynamic sampling used for this statement&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Estadísticas&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;        258  recursive calls&lt;br /&gt;       1667  db block gets&lt;br /&gt;       1436  consistent gets&lt;br /&gt;       1028  physical reads&lt;br /&gt;    7656052  redo size&lt;br /&gt;        847  bytes sent via SQL*Net to client&lt;br /&gt;        574  bytes received via SQL*Net from client&lt;br /&gt;          6  SQL*Net roundtrips to/from client&lt;br /&gt;          7  sorts (memory)&lt;br /&gt;          0  sorts (disk)&lt;br /&gt;      50000  rows processed&lt;br /&gt;&lt;br /&gt;rop@DESA11G&gt; commit;&lt;br /&gt;&lt;br /&gt;Confirmación terminada.&lt;br /&gt;&lt;br /&gt;rop@DESA11G&gt; ed&lt;br /&gt;Escrito file afiedt.buf&lt;br /&gt;&lt;br /&gt;  1* update t_c_dss set y='C' where y = 'B' and rownum &lt;= 50000&lt;br /&gt;rop@DESA11G&gt; /&lt;br /&gt;&lt;br /&gt;50000 filas actualizadas.&lt;br /&gt;&lt;br /&gt;Transcurrido: 00:00:01.84&lt;br /&gt;&lt;br /&gt;Plan de Ejecución&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;Plan hash value: 2179303604&lt;br /&gt;&lt;br /&gt;-------------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation           | Name    | Rows  | Bytes | Cost (%CPU)| Time     |&lt;br /&gt;-------------------------------------------------------------------------------&lt;br /&gt;|   0 | UPDATE STATEMENT    |         | 50000 |  1562K|  9285   (3)| 00:01:52 |&lt;br /&gt;|   1 |  UPDATE             | T_C_DSS |       |       |            |          |&lt;br /&gt;|*  2 |   COUNT STOPKEY     |         |       |       |            |          |&lt;br /&gt;|*  3 |    TABLE ACCESS FULL| T_C_DSS |  2512K|    76M|  9285   (3)| 00:01:52 |&lt;br /&gt;-------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;   2 - filter(ROWNUM&lt;=50000)&lt;br /&gt;   3 - filter("Y"='B')&lt;br /&gt;&lt;br /&gt;Note&lt;br /&gt;-----&lt;br /&gt;   - dynamic sampling used for this statement&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Estadísticas&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;        257  recursive calls&lt;br /&gt;      51518  db block gets&lt;br /&gt;        827  consistent gets&lt;br /&gt;        890  physical reads&lt;br /&gt;   15677976  redo size&lt;br /&gt;        867  bytes sent via SQL*Net to client&lt;br /&gt;        580  bytes received via SQL*Net from client&lt;br /&gt;          6  SQL*Net roundtrips to/from client&lt;br /&gt;          7  sorts (memory)&lt;br /&gt;          0  sorts (disk)&lt;br /&gt;      50000  rows processed&lt;br /&gt;&lt;br /&gt;rop@DESA11G&gt; commit;&lt;br /&gt;&lt;br /&gt;Confirmación terminada.&lt;br /&gt;&lt;br /&gt;Transcurrido: 00:00:00.01&lt;br /&gt;rop@DESA11G&gt; ed&lt;br /&gt;Escrito file afiedt.buf&lt;br /&gt;&lt;br /&gt;  1* update t_c_oltp set y='C' where y = 'B' and rownum &lt;= 50000&lt;br /&gt;rop@DESA11G&gt; /&lt;br /&gt;&lt;br /&gt;50000 filas actualizadas.&lt;br /&gt;&lt;br /&gt;Transcurrido: 00:01:09.89&lt;br /&gt;&lt;br /&gt;Plan de Ejecución&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;Plan hash value: 2618608701&lt;br /&gt;&lt;br /&gt;--------------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation           | Name     | Rows  | Bytes | Cost (%CPU)| Time     |&lt;br /&gt;--------------------------------------------------------------------------------&lt;br /&gt;|   0 | UPDATE STATEMENT    |          | 50000 |  1562K|  3791   (8)| 00:00:46 |&lt;br /&gt;|   1 |  UPDATE             | T_C_OLTP |       |       |            |          |&lt;br /&gt;|*  2 |   COUNT STOPKEY     |          |       |       |            |          |&lt;br /&gt;|*  3 |    TABLE ACCESS FULL| T_C_OLTP |  2814K|    85M|  3791   (8)| 00:00:46 |&lt;br /&gt;--------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;   2 - filter(ROWNUM&lt;=50000)&lt;br /&gt;   3 - filter("Y"='B')&lt;br /&gt;&lt;br /&gt;Note&lt;br /&gt;-----&lt;br /&gt;   - dynamic sampling used for this statement&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Estadísticas&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;        411  recursive calls&lt;br /&gt;    6153098  db block gets&lt;br /&gt;    4894807  consistent gets&lt;br /&gt;       1567  physical reads&lt;br /&gt;   40901448  redo size&lt;br /&gt;        868  bytes sent via SQL*Net to client&lt;br /&gt;        581  bytes received via SQL*Net from client&lt;br /&gt;          6  SQL*Net roundtrips to/from client&lt;br /&gt;          7  sorts (memory)&lt;br /&gt;          0  sorts (disk)&lt;br /&gt;      50000  rows processed&lt;br /&gt;&lt;br /&gt;rop@DESA11G&gt; commit;&lt;br /&gt;&lt;br /&gt;Confirmación terminada.&lt;br /&gt;&lt;br /&gt;Transcurrido: 00:00:00.01&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Los tiempos del update de la columna y en cada caso fueron:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;           Tiempo   Redo   Db block gets&lt;br /&gt;           ------   ----   -------------&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;T          1s        7Mb   1667&lt;br /&gt;T_C_DSS    1s       15Mb   51518&lt;br /&gt;T_C_OLTP   1m 9s    39Mb   6153098&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Los tiempos en la tabla oltp para el update masivo son muy superiores con respecto a los tiempos de la misma operacion sobre las otras tablas. Intenté probar de realizar el mismo update pero con 1M de filas y si bien para las tablas t y dss los tiempos fueron de menos de 2 minutos para el caso de la tabla oltp no terminó y pasadas 2 horas tuve que matar la sesión. Lo probé 3 veces en dias distintos y en los tres casos tuve que suspender la ejecución.&lt;br /&gt;&lt;br /&gt;En el ultimo test voy a ver como se comportan los select's. Para ver detalle voy a activar el trace 10046. La sentencia que armé recorre toda la tabla:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA11G&gt;  ALTER SESSION SET EVENTS '10046 trace name context forever, level 8';&lt;br /&gt;&lt;br /&gt;Sesión modificada.&lt;br /&gt;&lt;br /&gt;Transcurrido: 00:00:00.07&lt;br /&gt;rop@DESA11G&gt; ed&lt;br /&gt;Escrito file afiedt.buf&lt;br /&gt;&lt;br /&gt;  1  select z,count(*),max(z)&lt;br /&gt;  2  from t&lt;br /&gt;  3* group by z&lt;br /&gt;rop@DESA11G&gt; /&lt;br /&gt;&lt;br /&gt;Z           COUNT(*) MAX(Z)&lt;br /&gt;--------- ---------- ---------&lt;br /&gt;18-FEB-10     666293 18-FEB-10&lt;br /&gt;17-FEB-10     665982 17-FEB-10&lt;br /&gt;20-FEB-10     667869 20-FEB-10&lt;br /&gt;16-FEB-10     666656 16-FEB-10&lt;br /&gt;23-FEB-10     665526 23-FEB-10&lt;br /&gt;19-FEB-10     666827 19-FEB-10&lt;br /&gt;22-FEB-10     665960 22-FEB-10&lt;br /&gt;21-FEB-10     666494 21-FEB-10&lt;br /&gt;24-FEB-10     668393 24-FEB-10&lt;br /&gt;&lt;br /&gt;9 filas seleccionadas.&lt;br /&gt;&lt;br /&gt;Transcurrido: 00:00:09.89&lt;br /&gt;rop@DESA11G&gt; ed&lt;br /&gt;Escrito file afiedt.buf&lt;br /&gt;&lt;br /&gt;********************************************************************************&lt;br /&gt;&lt;br /&gt;select z,count(*),max(z)&lt;br /&gt;from t&lt;br /&gt;group by z&lt;br /&gt;&lt;br /&gt;call     count       cpu    elapsed       disk      query    current        rows&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;Parse        1      0.02       0.01          2          2          0           0&lt;br /&gt;Execute      1      0.00       0.00          0          0          0           0&lt;br /&gt;Fetch        2     10.33       9.55      41379      42190          0           9&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;total        4     10.35       9.57      41381      42192          0           9&lt;br /&gt;&lt;br /&gt;Misses in library cache during parse: 1&lt;br /&gt;Optimizer mode: ALL_ROWS&lt;br /&gt;Parsing user id: 82&lt;br /&gt;&lt;br /&gt;Rows     Row Source Operation&lt;br /&gt;-------  ---------------------------------------------------&lt;br /&gt;      9  HASH GROUP BY (cr=42190 pr=41379 pw=0 time=0 us cost=11949 size=44228052 card=4914228)&lt;br /&gt;6000000   TABLE ACCESS FULL T (cr=42190 pr=41379 pw=0 time=29090 us cost=11414 size=44228052 card=4914228)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Elapsed times include waiting on following events:&lt;br /&gt;  Event waited on                             Times   Max. Wait  Total Waited&lt;br /&gt;  ----------------------------------------   Waited  ----------  ------------&lt;br /&gt;  db file sequential read                         2        0.00          0.00&lt;br /&gt;  SQL*Net message to client                       2        0.00          0.00&lt;br /&gt;  reliable message                                1        0.00          0.00&lt;br /&gt;  enq: KO - fast object checkpoint                1        0.00          0.00&lt;br /&gt;  direct path read                              334        0.00          0.02&lt;br /&gt;  SQL*Net message from client                     2        0.02          0.02&lt;br /&gt;********************************************************************************&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;select z,count(*),max(z)&lt;br /&gt;from t_c_dss&lt;br /&gt;group by z&lt;br /&gt;&lt;br /&gt;call     count       cpu    elapsed       disk      query    current        rows&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;Parse        1      0.01       0.01          2          2          0           0&lt;br /&gt;Execute      1      0.00       0.00          0          0          0           0&lt;br /&gt;Fetch        2     10.71      10.02      33071      33680          0           9&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;total        4     10.72      10.04      33073      33682          0           9&lt;br /&gt;&lt;br /&gt;Misses in library cache during parse: 1&lt;br /&gt;Optimizer mode: ALL_ROWS&lt;br /&gt;Parsing user id: 82&lt;br /&gt;&lt;br /&gt;Rows     Row Source Operation&lt;br /&gt;-------  ---------------------------------------------------&lt;br /&gt;      9  HASH GROUP BY (cr=33680 pr=33071 pw=0 time=0 us cost=9723 size=46852029 card=5205781)&lt;br /&gt;6000000   TABLE ACCESS FULL T_C_DSS (cr=33680 pr=33071 pw=0 time=0 us cost=9155 size=46852029 card=5205781)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Elapsed times include waiting on following events:&lt;br /&gt;  Event waited on                             Times   Max. Wait  Total Waited&lt;br /&gt;  ----------------------------------------   Waited  ----------  ------------&lt;br /&gt;  SQL*Net message to client                       2        0.00          0.00&lt;br /&gt;  reliable message                                1        0.00          0.00&lt;br /&gt;  enq: KO - fast object checkpoint                1        0.00          0.00&lt;br /&gt;  direct path read                              270        0.01          0.02&lt;br /&gt;  SQL*Net message from client                     2        0.02          0.02&lt;br /&gt;********************************************************************************&lt;br /&gt;&lt;br /&gt;select z,count(*),max(z)&lt;br /&gt;from t_c_oltp&lt;br /&gt;group by z&lt;br /&gt;&lt;br /&gt;call     count       cpu    elapsed       disk      query    current        rows&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;Parse        1      0.01       0.00          2          2          0           0&lt;br /&gt;Execute      1      0.00       0.00          0          0          0           0&lt;br /&gt;Fetch        2      9.82       9.42      12852      13635          0           9&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;total        4      9.83       9.42      12854      13637          0           9&lt;br /&gt;&lt;br /&gt;Misses in library cache during parse: 1&lt;br /&gt;Optimizer mode: ALL_ROWS&lt;br /&gt;Parsing user id: 82&lt;br /&gt;&lt;br /&gt;Rows     Row Source Operation&lt;br /&gt;-------  ---------------------------------------------------&lt;br /&gt;      9  HASH GROUP BY (cr=13635 pr=12852 pw=0 time=0 us cost=4271 size=50894532 card=5654948)&lt;br /&gt;6000000   TABLE ACCESS FULL T_C_OLTP (cr=13635 pr=12852 pw=0 time=0 us cost=3651 size=50894532 card=5654948)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Elapsed times include waiting on following events:&lt;br /&gt;  Event waited on                             Times   Max. Wait  Total Waited&lt;br /&gt;  ----------------------------------------   Waited  ----------  ------------&lt;br /&gt;  SQL*Net message to client                       2        0.00          0.00&lt;br /&gt;  reliable message                                1        0.00          0.00&lt;br /&gt;  enq: KO - fast object checkpoint                1        0.00          0.00&lt;br /&gt;  direct path read                              110        0.00          0.00&lt;br /&gt;  SQL*Net message from client                     2        0.02          0.02&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Los tiempos en resolver la consulta fueron:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;T        = 9.57s&lt;br /&gt;T_C_DSS  = 10.04s&lt;br /&gt;T_C_OLTP = 9.42s&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;El select sobre la tabla oltp fue el que menos tiempo arrojó, demostrando que no hubo overhead por el tema de la compresión.&lt;br /&gt;&lt;br /&gt;Como conclusión general se ve que para operaciones masivas los tiempos y el consumo de redo sobre la tabla oltp son importantes y hay que tenerlos en cuenta. Tambien consideremos que este mecanismo, tal cual su nombre lo infiere, esta pensado para bases oltp en donde no es común el cambio masivo, aunque tampoco es tan infrecuente en ciertos casos, ya que muchas bases hibridas se comportan como oltp (por ejemplo durante el dia) y por la noche se usan como DSS con importante procesamiento batch que generalmente produce cambios masivos de datos. &lt;br /&gt;&lt;br /&gt;En el paper oficial: &lt;span style="color: rgb(255, 0, 0); font-weight: bold;"&gt; &lt;/span&gt;&lt;a style="color: rgb(255, 0, 0); font-weight: bold;" href="http://www.oracle.com/technology/products/database/oracle11g/pdf/advanced-compression-whitepaper.pdf" rel="stylesheet"&gt;"Advanced Compression with Oracle 11g R2"&lt;/a&gt; dice que las escrituras puntuales no se comprimen en cada operación y que la compresión de todo el bloque se realiza en forma batch cuando se alcanza un umbral en el bloque. Seguramente por ese motivo en mi test los tiempos fueron excesivos ya que al insertar o modificar muchas filas se alcanzó el umbral en varios bloques disparando la compresión en vivo y sumando tiempo al procesamiento de la sentencia.&lt;br /&gt;&lt;br /&gt;A mi siempre me gusta testear a fondo cada nuevo feature para ver si lo puedo recomendar a mis clientes ya que no todo lo que brilla es oro y a veces existen restricciones que lo hacen inaplicable en ciertos negocios. Además hay que considerar el tema economico, la compresión avanzada, a diferencia de la compresión básica que viene habilitada para usar con la licencia de Enterprise Edition, se paga como un opcional aparte y por lo tanto es un factor no menor que merece se analizado antes de implementar este nuevo feature.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5727738329548735676-8326882933522632522?l=oramdq.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://oramdq.blogspot.com/feeds/8326882933522632522/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://oramdq.blogspot.com/2010/02/compresion-avanzada-de-tablas-en-11g.html#comment-form' title='1 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/8326882933522632522'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/8326882933522632522'/><link rel='alternate' type='text/html' href='http://oramdq.blogspot.com/2010/02/compresion-avanzada-de-tablas-en-11g.html' title='Compresión avanzada de Tablas en 11g (OLTP Compression)'/><author><name>Pablo A. Rovedo</name><uri>http://www.blogger.com/profile/02924687287216878757</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://3.bp.blogspot.com/_scsqOe7UOdc/SYDOV9ANLfI/AAAAAAAAAFQ/71M2XO1AZ1M/S220/DSC02067+(Small).JPG'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5727738329548735676.post-3885153238222378679</id><published>2010-02-12T10:15:00.000-08:00</published><updated>2010-02-19T03:52:42.285-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Conceptos'/><category scheme='http://www.blogger.com/atom/ns#' term='Performance'/><title type='text'>Evolución de los mecanismos para Estabilizar Planes  (graciosa comparación)</title><content type='html'>Mirando un ppt de una presentación de los nuevos features de 11g me gustó uno de los slides donde se hace una especie de parodia sobre la evolución de los mecanismos para estabilizar los planes de ejecución que se fueron agregando en las distintas versiones. Se refiere a Larry (Ellison) que es el fundador y dueño de Oracle comparandolo con Dios y la creación de la tierra. Me pareció muy gracioso, y seguramente van a entender la sutileza todos aquellos que estén en el mundillo Oracle, en especial los que nos dedicamos a temas de performance que muchas veces admiramos la forma en que trabaja el optimizador y otras, sinceramente no entendemos porque toma ciertas decisiones que lo llevan a armar un plan desastroso, en fin... ahi va la evolución de los mecanismos de estabilidad:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;In the beginning was the RULE …  &lt;br /&gt;On the Second Day, Larry Created the CBO …  (7)&lt;br /&gt;On the Third Day, Larry Created the Hint …   (7)  &lt;br /&gt;On the Fourth Day, Larry Created the Outline …  (8)&lt;br /&gt;… and Larry Saw That it Was Good&lt;br /&gt;On the Fifth Day, Larry Created the Profile …  (10g)&lt;br /&gt;On the Sixth Day, Larry Created the Baseline  (11g)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5727738329548735676-3885153238222378679?l=oramdq.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://oramdq.blogspot.com/feeds/3885153238222378679/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://oramdq.blogspot.com/2010/02/evolucion-de-los-planes-de-estabilidad.html#comment-form' title='0 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/3885153238222378679'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/3885153238222378679'/><link rel='alternate' type='text/html' href='http://oramdq.blogspot.com/2010/02/evolucion-de-los-planes-de-estabilidad.html' title='Evolución de los mecanismos para Estabilizar Planes  (graciosa comparación)'/><author><name>Pablo A. Rovedo</name><uri>http://www.blogger.com/profile/02924687287216878757</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://3.bp.blogspot.com/_scsqOe7UOdc/SYDOV9ANLfI/AAAAAAAAAFQ/71M2XO1AZ1M/S220/DSC02067+(Small).JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5727738329548735676.post-6956502700367721882</id><published>2010-02-05T07:05:00.000-08:00</published><updated>2010-02-05T11:03:59.223-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='11g New Features'/><category scheme='http://www.blogger.com/atom/ns#' term='Diagnóstico'/><category scheme='http://www.blogger.com/atom/ns#' term='Tuning'/><title type='text'>Colorear una sentencia sql (Colored SQL)</title><content type='html'>Muchos se preguntaran que significa "colorear un sentencia sql", verdad?. Lo que implica colorear es ni mas ni menos que marcar una sentencia identificandola por su sqlid, que es la identificación unica de una sentencia en la base da datos, para que los snapshots de AWR la incluyan en el repositorio y luego ser analizada. El repositorio de AWR almacena, entre otras cosas, las sentencias TOP, es decir las que mas consumieron entre dos snapshots, por default los snapshots se sacan cada hora, por lo que permite analizar por periodos de una hora. Si yo quisiera ver como se fue comportando una sentencia a lo largo de cierto tiempo pero dicha sentencia no esta dentro de las mas consumidoras no quedará registro y por lo tanto no se podrá analizar su actividad a posteriori. Para asegurar que se le siga el rastro a las ejecuciones de un sentencia, sin importar cuanto consume, a partir de 11g se la puede "colorear", veamos como es esto:&lt;br /&gt;&lt;br /&gt;Armo una consulta bien sencilla, que obviamente no consumirá mucho y no quedará registrada en una base con una minima actividad:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA11G&gt; select 'TEST COLORED SQL' from dual;&lt;br /&gt;&lt;br /&gt;'TESTCOLOREDSQL'&lt;br /&gt;----------------&lt;br /&gt;TEST COLORED SQL&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Buscamos el sqlid asociado:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA11G&gt; set line 120&lt;br /&gt;rop@DESA11G&gt;  select sql_text,sql_id from v$sqlstats where sql_text like '%TEST COLORED SQL%';&lt;br /&gt;&lt;br /&gt;SQL_TEXT&lt;br /&gt;------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;SQL_ID&lt;br /&gt;-------------&lt;br /&gt; select sql_text,sql_id from v$sqlstats where sql_text like '%TEST COLORED SQL%'&lt;br /&gt;f2c39t3uct6vp&lt;br /&gt;&lt;br /&gt;select 'TEST COLORED SQL' from dual&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;0fm46pj2s9vux&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Una vez obtenido el sqlid voy a marcarla o colorearla de la siguiente forma:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA11G&gt; ed&lt;br /&gt;Escrito file afiedt.buf&lt;br /&gt;&lt;br /&gt;  1  begin&lt;br /&gt;  2    dbms_workload_repository.add_colored_sql(sql_id =&gt; '0fm46pj2s9vux'&lt;br /&gt;  3    );&lt;br /&gt;  4* end;&lt;br /&gt;rop@DESA11G&gt; /&lt;br /&gt;&lt;br /&gt;Procedimiento PL/SQL terminado correctamente.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Ahora tomo un snapshot y luego ejecuto 3 veces la sentencia coloreada:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA11G&gt; begin&lt;br /&gt;  2      dbms_workload_repository.create_snapshot;&lt;br /&gt;  3  end;&lt;br /&gt;  4  /&lt;br /&gt;&lt;br /&gt;Procedimiento PL/SQL terminado correctamente.&lt;br /&gt;&lt;br /&gt;rop@DESA11G&gt; select 'TEST COLORED SQL' from dual;&lt;br /&gt;&lt;br /&gt;'TESTCOLOREDSQL'&lt;br /&gt;----------------&lt;br /&gt;TEST COLORED SQL&lt;br /&gt;&lt;br /&gt;rop@DESA11G&gt; /&lt;br /&gt;&lt;br /&gt;'TESTCOLOREDSQL'&lt;br /&gt;----------------&lt;br /&gt;TEST COLORED SQL&lt;br /&gt;&lt;br /&gt;rop@DESA11G&gt; /&lt;br /&gt;&lt;br /&gt;'TESTCOLOREDSQL'&lt;br /&gt;----------------&lt;br /&gt;TEST COLORED SQL&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Tomo otro snapshot luego de aproximadamente 15' y veo si aparece:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA11G&gt; begin&lt;br /&gt;  2      dbms_workload_repository.create_snapshot;&lt;br /&gt;  3  end;&lt;br /&gt;  4  /&lt;br /&gt;&lt;br /&gt;Procedimiento PL/SQL terminado correctamente.&lt;br /&gt;&lt;br /&gt;rop@DESA11G&gt; select executions_delta,cpu_time_delta,elapsed_time_delta from dba_hist_sqlstat&lt;br /&gt;  2  where snap_id = (select max(snap_id) from dba_hist_sqlstat)&lt;br /&gt;  3    and sql_id = '0fm46pj2s9vux';&lt;br /&gt;&lt;br /&gt;EXECUTIONS_DELTA CPU_TIME_DELTA ELAPSED_TIME_DELTA&lt;br /&gt;---------------- -------------- ------------------&lt;br /&gt;               3              0                  0&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;La sentencia apareció y se ve que consumió tan poco que no se llegó a registrar tiempo&lt;br /&gt;Para desmarcarla hago lo siguiente:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA11G&gt; ed&lt;br /&gt;Escrito file afiedt.buf&lt;br /&gt;&lt;br /&gt;  1  begin&lt;br /&gt;  2    dbms_workload_repository.remove_colored_sql(sql_id =&gt; '0fm46pj2s9vux');&lt;br /&gt;  3* end;&lt;br /&gt;rop@DESA11G&gt; /&lt;br /&gt;&lt;br /&gt;Procedimiento PL/SQL terminado correctamente.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Repito el proceso pero sin la consulta "coloreada":&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA11G&gt; ed&lt;br /&gt;Escrito file afiedt.buf&lt;br /&gt;&lt;br /&gt;  1  begin&lt;br /&gt;  2      dbms_workload_repository.create_snapshot;&lt;br /&gt;  3* end;&lt;br /&gt;rop@DESA11G&gt; /&lt;br /&gt;&lt;br /&gt;Procedimiento PL/SQL terminado correctamente.&lt;br /&gt;&lt;br /&gt;rop@DESA11G&gt; select 'TEST COLORED SQL' from dual;&lt;br /&gt;&lt;br /&gt;'TESTCOLOREDSQL'&lt;br /&gt;----------------&lt;br /&gt;TEST COLORED SQL&lt;br /&gt;&lt;br /&gt;rop@DESA11G&gt; /&lt;br /&gt;&lt;br /&gt;'TESTCOLOREDSQL'&lt;br /&gt;----------------&lt;br /&gt;TEST COLORED SQL&lt;br /&gt;&lt;br /&gt;rop@DESA11G&gt; /&lt;br /&gt;&lt;br /&gt;'TESTCOLOREDSQL'&lt;br /&gt;----------------&lt;br /&gt;TEST COLORED SQL&lt;br /&gt;&lt;br /&gt;rop@DESA11G&gt; begin&lt;br /&gt;  2      dbms_workload_repository.create_snapshot;&lt;br /&gt;  3  end;&lt;br /&gt;  4  /&lt;br /&gt;&lt;br /&gt;Procedimiento PL/SQL terminado correctamente.&lt;br /&gt;&lt;br /&gt;rop@DESA11G&gt; ed&lt;br /&gt;Escrito file afiedt.buf&lt;br /&gt;&lt;br /&gt;  1  select executions_delta,cpu_time_delta,elapsed_time_delta from dba_hist_sqlstat&lt;br /&gt;  2  where snap_id = (select max(snap_id) from dba_hist_sqlstat)&lt;br /&gt;  3*   and sql_id = '0fm46pj2s9vux'&lt;br /&gt;rop@DESA11G&gt; /&lt;br /&gt;&lt;br /&gt;ninguna fila seleccionada&lt;br /&gt;&lt;br /&gt;rop@DESA11G&gt; &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Como se observa ahora no se registró en AWR, ya que como se vió es una sentencia con consumo nulo y que obviamente no califica entre las Top para ser persistida en el repositorio.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5727738329548735676-6956502700367721882?l=oramdq.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://oramdq.blogspot.com/feeds/6956502700367721882/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://oramdq.blogspot.com/2010/02/colorear-una-sentencia-sql-colored-sql.html#comment-form' title='0 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/6956502700367721882'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/6956502700367721882'/><link rel='alternate' type='text/html' href='http://oramdq.blogspot.com/2010/02/colorear-una-sentencia-sql-colored-sql.html' title='Colorear una sentencia sql (Colored SQL)'/><author><name>Pablo A. Rovedo</name><uri>http://www.blogger.com/profile/02924687287216878757</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://3.bp.blogspot.com/_scsqOe7UOdc/SYDOV9ANLfI/AAAAAAAAAFQ/71M2XO1AZ1M/S220/DSC02067+(Small).JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5727738329548735676.post-7927528431539506754</id><published>2010-02-02T06:56:00.000-08:00</published><updated>2010-02-03T04:17:38.381-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='11g New Features'/><category scheme='http://www.blogger.com/atom/ns#' term='CBO'/><category scheme='http://www.blogger.com/atom/ns#' term='Performance'/><title type='text'>Extendiendo la información estadistica en 11g (estadisticas multicolumna)</title><content type='html'>Una vez mas escribo una nota relacionada con el optimizador de Oracle y en especial sobre las estadisticas, que como ya se sabe son el pilar fundamental para garantizar un plan optimo. Asi como un matematico se basa en axiomas o teoremas para demostrar otro teorema en base a inferencias logicas, el optimizador utiliza la información estadistica que tiene a su disposición para inferir el plan de ejecución mas conveniente, el que menos recurso insume. En 11g se pueden suministrar extensiones a las estadisticas habituales para "ayudar" en ciertos casos particulares. Uno de los problemas que se daban esta relacionado con la correlación entre la información estadisticas de multiples columnas que se referencian en un predicado. Para entender mejor voy a mostrar un ejemplo completo (en una nota de diciembre habia escrito sobre estaditicas multicolumnas para mostrar SQL Profiles, pero esta nueva nota ahonda en mas detalle, ver nota: &lt;span style="color: rgb(255, 0, 0); font-weight: bold;"&gt; &lt;/span&gt;&lt;a style="color: rgb(255, 0, 0); font-weight: bold;" href="http://oramdq.blogspot.com/2009/12/sql-profiles-una-ayuda-para-que-el.html" type="text/css" rel="stylesheet"&gt;SQL Profiles. Una ayuda adicional...&lt;/a&gt;)&lt;br /&gt;&lt;br /&gt;Primero, como es habitual, voy a crear una tabla T en base a los registros de la tabla DBA_OBJECTS y voy a crear un indice por dos columnas elegidas arbitrariamente:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA11G&gt; create table t as select * from dba_objects;&lt;br /&gt;&lt;br /&gt;Tabla creada.&lt;br /&gt;&lt;br /&gt;rop@DESA11G&gt; create index t_idx on t(owner,object_type);&lt;br /&gt;&lt;br /&gt;Índice creado.&lt;br /&gt;&lt;br /&gt;rop@DESA11G&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Ahora vamos a ver la distribución de las columnas OWNER y OBJECT_TYPE:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA11G&gt; select owner,count(1)&lt;br /&gt;2          from t&lt;br /&gt;3          group by owner;&lt;br /&gt;&lt;br /&gt;OWNER                            COUNT(1)&lt;br /&gt;------------------------------ ----------&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;PUBLIC                              26723&lt;/span&gt;&lt;br /&gt;SYSTEM                                518&lt;br /&gt;XDB                                   811&lt;br /&gt;OLAPSYS                               720&lt;br /&gt;FLOWS_FILES                            12&lt;br /&gt;SYS                                 30217&lt;br /&gt;TSMSYS                                  3&lt;br /&gt;MDSYS                                1303&lt;br /&gt;SYSMAN                               3360&lt;br /&gt;EXFSYS                                303&lt;br /&gt;SI_INFORMTN_SCHEMA                      8&lt;br /&gt;ORACLE_OCM                              8&lt;br /&gt;WMSYS                                 315&lt;br /&gt;ORDSYS                               2353&lt;br /&gt;SCOTT                                   6&lt;br /&gt;WK_TEST                                47&lt;br /&gt;FLOWS_030000                         1526&lt;br /&gt;CTXSYS                                372&lt;br /&gt;ORDPLUGINS                             10&lt;br /&gt;WKSYS                                 371&lt;br /&gt;ROP                                   122&lt;br /&gt;OUTLN                                   9&lt;br /&gt;DBSNMP                                 55&lt;br /&gt;&lt;br /&gt;23 filas seleccionadas.&lt;br /&gt;&lt;br /&gt;rop@DESA11G&gt; select object_type,count(1)&lt;br /&gt;2          from t&lt;br /&gt;3          group by object_type;&lt;br /&gt;&lt;br /&gt;OBJECT_TYPE           COUNT(1)&lt;br /&gt;------------------- ----------&lt;br /&gt;INDEX                     3228&lt;br /&gt;JOB CLASS                   13&lt;br /&gt;CONTEXT                      7&lt;br /&gt;TABLE SUBPARTITION          40&lt;br /&gt;TYPE BODY                  238&lt;br /&gt;INDEXTYPE                   11&lt;br /&gt;PROCEDURE                  135&lt;br /&gt;RESOURCE PLAN                7&lt;br /&gt;RULE                         1&lt;br /&gt;JAVA CLASS               22205&lt;br /&gt;TABLE PARTITION            289&lt;br /&gt;SCHEDULE                     2&lt;br /&gt;WINDOW                       9&lt;br /&gt;WINDOW GROUP                 4&lt;br /&gt;JAVA RESOURCE              835&lt;br /&gt;TABLE                     2576&lt;br /&gt;TYPE                      2643&lt;br /&gt;VIEW                      4788&lt;br /&gt;LIBRARY                    181&lt;br /&gt;FUNCTION                   296&lt;br /&gt;TRIGGER                    484&lt;br /&gt;PROGRAM                     18&lt;br /&gt;MATERIALIZED VIEW            1&lt;br /&gt;JAVA SOURCE                  1&lt;br /&gt;CLUSTER                     10&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;SYNONYM                  26795&lt;/span&gt;&lt;br /&gt;PACKAGE BODY              1213&lt;br /&gt;CONSUMER GROUP              14&lt;br /&gt;EVALUATION CONTEXT          11&lt;br /&gt;QUEUE                       35&lt;br /&gt;RULE SET                    17&lt;br /&gt;DIRECTORY                    3&lt;br /&gt;EDITION                      1&lt;br /&gt;OPERATOR                    57&lt;br /&gt;UNDEFINED                    6&lt;br /&gt;JAVA DATA                  325&lt;br /&gt;SEQUENCE                   230&lt;br /&gt;LOB                        768&lt;br /&gt;PACKAGE                   1274&lt;br /&gt;INDEX PARTITION            289&lt;br /&gt;LOB PARTITION                7&lt;br /&gt;JOB                         11&lt;br /&gt;XML SCHEMA                  94&lt;br /&gt;&lt;br /&gt;43 filas seleccionadas.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;De la distribución mostrada podemos ver que tenemos 30210 filas cuyo owner es SYS y 26795 cuyo object_type es SYNONYM. Analizando por separadas ambas distribuciones vemos que son un porcentaje alto del total de cada agrupación y evaluadas por separado suena coherente el acceso full scan cuando se filtra por dichas columnas para los valores analizados.&lt;br /&gt;Ahora veamos que pasa si en un mismo predicado filtramos por owner y object_type:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA11G&gt; select count(1) from t where owner = 'SYS' and object_type = 'SYNONYM';&lt;br /&gt;&lt;br /&gt;COUNT(1)&lt;br /&gt;----------&lt;br /&gt;      9&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Observamos que combinando las dos columnas solo cumplen dicho filtro 9 filas.&lt;br /&gt;Voy a recolectar estadisticas y analizar el plan:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA11G&gt; begin&lt;br /&gt;2             dbms_stats.gather_table_Stats(user,&lt;br /&gt;3                              'T',&lt;br /&gt;4                              method_opt =&gt; 'for all columns size skewonly');&lt;br /&gt;5* end;&lt;br /&gt;rop@DESA11G&gt; /&lt;br /&gt;&lt;br /&gt;rop@DESA11G&gt; explain plan for&lt;br /&gt;2          select *&lt;br /&gt;3          from t&lt;br /&gt;4          where owner = 'SYS' and object_type = 'SYNONYM';&lt;br /&gt;&lt;br /&gt;Explicado.&lt;br /&gt;&lt;br /&gt;rop@DESA11G&gt; select * from table(dbms_xplan.display);&lt;br /&gt;&lt;br /&gt;PLAN_TABLE_OUTPUT&lt;br /&gt;---------------------------------------------------------------------------------------------&lt;br /&gt;Plan hash value: 2153619298&lt;br /&gt;&lt;br /&gt;--------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |&lt;br /&gt;--------------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT  |      | 11996 |  1183K|   288   (2)| 00:00:04 |&lt;br /&gt;|*  1 |  TABLE ACCESS FULL| T    | 11996 |  1183K|   288   (2)| 00:00:04 |&lt;br /&gt;--------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;1 - filter("OBJECT_TYPE"='SYNONYM' AND "OWNER"='SYS')&lt;br /&gt;&lt;br /&gt;13 filas seleccionadas.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Estimó 11996 lo cual es muy impreciso, no?, deberia ser 9 o cercano para se mas real. Por que se confundió tanto el optimizador y eligió ir por full scan?. Miremos la salida del trace con el evento 10053, que nos muestra en detalle los pasos que sigue el optimizador para decidir que hacer. Abajo copio solo la parte del trace que nos interesa (el trace completo es muy extenso y lista parametrizaciones, transformaciones,orden de evaluación de predicados, etc):&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;***************************************&lt;br /&gt;SINGLE TABLE ACCESS PATH&lt;br /&gt;Single Table Cardinality Estimation for T[T]&lt;br /&gt;Column (#1):&lt;br /&gt; NewDensity:0.000091, OldDensity:0.000007 BktCnt:5480, PopBktCnt:5478, PopValCnt:16, NDV:23&lt;br /&gt;Column (#6):&lt;br /&gt; NewDensity:0.000091, OldDensity:0.000007 BktCnt:5480, PopBktCnt:5475, PopValCnt:26, NDV:43&lt;br /&gt;ColGroup (#1, Index) T_IDX&lt;br /&gt; Col#: 1 6    CorStregth: 3.96&lt;br /&gt;ColGroup Usage:: PredCnt: 2  Matches Full:  Partial:&lt;br /&gt;Table: T  Alias: T&lt;br /&gt; Card: Original: 69172.000000  Rounded: 11646  Computed: 11645.63  Non Adjusted: 11645.63&lt;br /&gt;&lt;span style="font-weight: bold;"&gt; Access Path: TableScan&lt;/span&gt;&lt;br /&gt; &lt;span style="font-weight: bold;"&gt;Cost:  288.43&lt;/span&gt;  Resp: 288.43  Degree: 0&lt;br /&gt;   Cost_io: 285.00  Cost_cpu: 31622103&lt;br /&gt;   Resp_io: 285.00  Resp_cpu: 31622103&lt;br /&gt;ColGroup Usage:: PredCnt: 2  Matches Full:  Partial:&lt;br /&gt;ColGroup Usage:: PredCnt: 2  Matches Full:  Partial:&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Access Path: index (AllEqRange)&lt;/span&gt;&lt;br /&gt; Index: T_IDX&lt;br /&gt; resc_io: 559.00  resc_cpu: 11318715&lt;br /&gt; ix_sel: 0.168358  ix_sel_with_filters: 0.168358&lt;br /&gt; &lt;span style="font-weight: bold;"&gt;Cost: 560.23&lt;/span&gt;  Resp: 560.23  Degree: 1&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Best:: AccessPath: TableScan&lt;br /&gt;      Cost: 288.43&lt;/span&gt;  Degree: 1  Resp: 288.43  Card: 11645.63  Bytes: 0&lt;br /&gt;&lt;br /&gt;***************************************&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;En el análisis el optimizador asigna un costo de 560 al plan que utiliza el indice y 288 al plan que utiliza el full scan, y como ya sabemos se queda con el que menor costo arroja, por lo tanto estima que es el mejor path.&lt;br /&gt;&lt;br /&gt;Afortunadamente en 11g se pueden recolectar estadisticas a nivel multicoluma lo cual aporta información de correlación muy util, veamos como hacerlo:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA11G&gt; declare&lt;br /&gt;2      out varchar2(30);&lt;br /&gt;3  begin&lt;br /&gt;4      out := dbms_stats.create_extended_stats(user,'T', '(owner,object_type)');&lt;br /&gt;5  end;&lt;br /&gt;6  /&lt;br /&gt;&lt;br /&gt;rop@DESA11G&gt; begin&lt;br /&gt;2      dbms_stats.gather_table_stats(null,'T',&lt;br /&gt;3      method_opt =&gt;'for all columns size auto for columns (owner,object_type)');&lt;br /&gt;4  end;&lt;br /&gt;5  /&lt;br /&gt;&lt;br /&gt;rop@DESA11G&gt; explain plan for&lt;br /&gt;2  select *&lt;br /&gt;3  from t&lt;br /&gt;4  where owner = 'SYS' and object_type = 'SYNONYM';&lt;br /&gt;&lt;br /&gt;Explicado.&lt;br /&gt;&lt;br /&gt;rop@DESA11G&gt; select * from table(dbms_xplan.display);&lt;br /&gt;&lt;br /&gt;PLAN_TABLE_OUTPUT&lt;br /&gt;----------------------------------------------------------------------------------------------------&lt;br /&gt;Plan hash value: 1020776977&lt;br /&gt;&lt;br /&gt;-------------------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |&lt;br /&gt;-------------------------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT            |       |     6 |   636 |     2   (0)| 00:00:01 |&lt;br /&gt;|   1 |  TABLE ACCESS BY INDEX ROWID| T     |     6 |   636 |     2   (0)| 00:00:01 |&lt;br /&gt;|*  2 |   INDEX RANGE SCAN          | T_IDX |     6 |       |     1   (0)| 00:00:01 |&lt;br /&gt;-------------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;2 - access("OWNER"='SYS' AND "OBJECT_TYPE"='SYNONYM')&lt;br /&gt;&lt;br /&gt;14 filas seleccionadas.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;El plan ahora uso el indice y ademas observar que la estimación de filas a retornar es de 6, lo cual es bastante cercano a la realidad. Veamos el trace del evento 10053 para analizar que hizo ahora el optimizador:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;***************************************&lt;br /&gt;SINGLE TABLE ACCESS PATH&lt;br /&gt;Single Table Cardinality Estimation for T[T]&lt;br /&gt;Column (#1):&lt;br /&gt; NewDensity:0.000091, OldDensity:0.000007 BktCnt:5480, PopBktCnt:5478, PopValCnt:16, NDV:23&lt;br /&gt;Column (#6):&lt;br /&gt; NewDensity:0.000091, OldDensity:0.000007 BktCnt:5480, PopBktCnt:5475, PopValCnt:26, NDV:43&lt;br /&gt;Column (#16):&lt;br /&gt; NewDensity:0.000091, OldDensity:0.000007 BktCnt:5480, PopBktCnt:5441, PopValCnt:104, NDV:250&lt;br /&gt;ColGroup (#1, VC) SYS_STUXJ8K0YTS_5QD1O0PEA514IY&lt;br /&gt; Col#: 1 6    CorStregth: 3.96&lt;br /&gt;ColGroup Usage:: PredCnt: 2  Matches Full:   Using density: 0.000091 of col #16 as selectivity of unpopular value pred&lt;br /&gt;#1  Partial:  Sel: 0.0001&lt;br /&gt;Table: T  Alias: T&lt;br /&gt; Card: Original: 69172.000000  Rounded: 6  Computed: 6.31  Non Adjusted: 6.31&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Access Path: TableScan&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt; Cost:  288.20&lt;/span&gt;  Resp: 288.20  Degree: 0&lt;br /&gt;   Cost_io: 285.00  Cost_cpu: 29526903&lt;br /&gt;   Resp_io: 285.00  Resp_cpu: 29526903&lt;br /&gt;ColGroup Usage:: PredCnt: 2  Matches Full:   Using density: 0.000091 of col #16 as selectivity of unpopular value pred&lt;br /&gt;#1  Partial:  Sel: 0.0001&lt;br /&gt;ColGroup Usage:: PredCnt: 2  Matches Full:   Using density: 0.000091 of col #16 as selectivity of unpopular value pred&lt;br /&gt;#1  Partial:  Sel: 0.0001&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Access Path: index (AllEqRange)&lt;/span&gt;&lt;br /&gt; Index: T_IDX&lt;br /&gt; resc_io: 2.00  resc_cpu: 19503&lt;br /&gt; ix_sel: 0.000091  ix_sel_with_filters: 0.000091&lt;br /&gt;&lt;span style="font-weight: bold;"&gt; Cost: 2.00 &lt;/span&gt; Resp: 2.00  Degree: 1&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Best:: AccessPath: IndexRange&lt;/span&gt;&lt;br /&gt;Index: T_IDX&lt;br /&gt;      &lt;span style="font-weight: bold;"&gt;Cost: 2.00&lt;/span&gt;  Degree: 1  Resp: 2.00  Card: 6.31  Bytes: 0&lt;br /&gt;&lt;br /&gt;***************************************&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;El costo del full scan sigue siendo, obviamente, el mismo (288) pero ahora el acceso por indice es de tan solo 2 y por lo tanto es el path elegido.&lt;br /&gt;Como les mostré en esta nota podemos garantizar un correcto funcionamiento para predicados con mas de un filtro recolectando las estadisticas extendidas, nuevas en 11g.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5727738329548735676-7927528431539506754?l=oramdq.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://oramdq.blogspot.com/feeds/7927528431539506754/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://oramdq.blogspot.com/2010/02/extendiendo-la-informacion-estadistica.html#comment-form' title='0 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/7927528431539506754'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/7927528431539506754'/><link rel='alternate' type='text/html' href='http://oramdq.blogspot.com/2010/02/extendiendo-la-informacion-estadistica.html' title='Extendiendo la información estadistica en 11g (estadisticas multicolumna)'/><author><name>Pablo A. Rovedo</name><uri>http://www.blogger.com/profile/02924687287216878757</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://3.bp.blogspot.com/_scsqOe7UOdc/SYDOV9ANLfI/AAAAAAAAAFQ/71M2XO1AZ1M/S220/DSC02067+(Small).JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5727738329548735676.post-9217478094687750572</id><published>2010-01-29T03:31:00.000-08:00</published><updated>2010-01-29T10:57:59.424-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Diagnóstico'/><category scheme='http://www.blogger.com/atom/ns#' term='Scripts'/><title type='text'>Enfoque para medir la disponibilidad efectiva de una base de datos Oracle</title><content type='html'>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.&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;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:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;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.&lt;/li&gt;&lt;li&gt;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.&lt;/li&gt;&lt;li&gt;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.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;create directory BDUMP as '/u01/app/oracle/admin/ROP102/bdump'&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;CREATE TABLE "ALERT_LOG"&lt;br /&gt;(  "TEXT" VARCHAR2(400)&lt;br /&gt;)&lt;br /&gt;ORGANIZATION EXTERNAL&lt;br /&gt;( TYPE ORACLE_LOADER&lt;br /&gt;DEFAULT DIRECTORY "BDUMP"&lt;br /&gt;ACCESS PARAMETERS&lt;br /&gt;( records delimited by newline&lt;br /&gt;nobadfile&lt;br /&gt;nodiscardfile&lt;br /&gt;nologfile&lt;br /&gt;  )&lt;br /&gt;LOCATION&lt;br /&gt;( 'alert_ROP102.log'&lt;br /&gt;)&lt;br /&gt;)&lt;br /&gt;REJECT LIMIT UNLIMITED;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;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:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;select min_date,&lt;br /&gt;max_date,&lt;br /&gt;round((max_date-min_date)*24,2) total,&lt;br /&gt;down,&lt;br /&gt;round(100-((down*100)/((max_date-min_date)*24)),3) pct_up&lt;br /&gt;from&lt;br /&gt;(select min(to_date(last_time,'Dy Mon DD HH24:MI:SS YYYY','NLS_DATE_LANGUAGE=english')) min_date,&lt;br /&gt;max(to_date(start_time,'Dy Mon DD HH24:MI:SS YYYY','NLS_DATE_LANGUAGE=english')) max_date,&lt;br /&gt;sum(round((to_date(start_time,'Dy Mon DD HH24:MI:SS YYYY','NLS_DATE_LANGUAGE=english')-&lt;br /&gt;to_date(last_time,'Dy Mon DD HH24:MI:SS YYYY','NLS_DATE_LANGUAGE=english'))*24,2)) down&lt;br /&gt;from&lt;br /&gt;(select start_time,&lt;br /&gt;last_time&lt;br /&gt;from&lt;br /&gt;(select text,&lt;br /&gt;lag(text,1) over (order by r) start_time,&lt;br /&gt;lag(text,2) over (order by r) last_time&lt;br /&gt;from ( select rownum r, text&lt;br /&gt;from alert_log&lt;br /&gt;where text like '___ ___ __ __:__:__ 20__'&lt;br /&gt;  or text like 'Starting ORACLE instance (normal)'))&lt;br /&gt;where text like 'Starting ORACLE instance (normal)'&lt;br /&gt;and last_time like '___ ___ __ __:__:__ 20__'&lt;br /&gt;and to_date(start_time,'Dy Mon DD HH24:MI:SS YYYY','NLS_DATE_LANGUAGE=english') &gt; sysdate-120))&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;MIN_DATE   MAX_DATE   TOTAL   DOWN  PCT_UP&lt;br /&gt;--------   --------   ------  ----  ------&lt;br /&gt;02/10/2009 29/01/2010 2858.22 22.45 &lt;span style="font-weight: bold;"&gt;99,214&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;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%.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5727738329548735676-9217478094687750572?l=oramdq.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://oramdq.blogspot.com/feeds/9217478094687750572/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://oramdq.blogspot.com/2010/01/enfoque-para-medir-la-disponibilidad.html#comment-form' title='0 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/9217478094687750572'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/9217478094687750572'/><link rel='alternate' type='text/html' href='http://oramdq.blogspot.com/2010/01/enfoque-para-medir-la-disponibilidad.html' title='Enfoque para medir la disponibilidad efectiva de una base de datos Oracle'/><author><name>Pablo A. Rovedo</name><uri>http://www.blogger.com/profile/02924687287216878757</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://3.bp.blogspot.com/_scsqOe7UOdc/SYDOV9ANLfI/AAAAAAAAAFQ/71M2XO1AZ1M/S220/DSC02067+(Small).JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5727738329548735676.post-4594418691347250207</id><published>2010-01-22T05:42:00.000-08:00</published><updated>2010-02-01T10:29:31.220-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='11g New Features'/><category scheme='http://www.blogger.com/atom/ns#' term='11g R2'/><category scheme='http://www.blogger.com/atom/ns#' term='SQL'/><title type='text'>Concatenar valores de una columna en 11g R2 (usando la función LISTAGG)</title><content type='html'>Varias veces me han preguntado como hacer para concatenar los valores de una columna en una sola fila agrupados por otra cierta columna. Para eso se necesita un operador de concatenación tal como existe el operador SUM() o el AVG() para sumar o sacar el promedio de un conjunto de columnas con cierto criterio de agrupamiento. Para solucionar esto lo que siempre sugeria era crear el operador STRAGG, que es una función que pueden encontrar en la pagina asktom.oracle.com.  Abajo voy a crear la función para mostrarles como funciona:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@ROP92&gt; create or replace type string_agg_type as object&lt;br /&gt;2  (&lt;br /&gt;3     total varchar2(4000),&lt;br /&gt;4&lt;br /&gt;5     static function&lt;br /&gt;6          ODCIAggregateInitialize(sctx IN OUT string_agg_type )&lt;br /&gt;7          return number,&lt;br /&gt;8&lt;br /&gt;9     member function&lt;br /&gt;10          ODCIAggregateIterate(self IN OUT string_agg_type ,&lt;br /&gt;11                               value IN varchar2 )&lt;br /&gt;12          return number,&lt;br /&gt;13&lt;br /&gt;14     member function&lt;br /&gt;15          ODCIAggregateTerminate(self IN string_agg_type,&lt;br /&gt;16                                 returnValue OUT  varchar2,&lt;br /&gt;17                                 flags IN number)&lt;br /&gt;18          return number,&lt;br /&gt;19&lt;br /&gt;20     member function&lt;br /&gt;21          ODCIAggregateMerge(self IN OUT string_agg_type,&lt;br /&gt;22                             ctx2 IN string_agg_type)&lt;br /&gt;23          return number&lt;br /&gt;24  );&lt;br /&gt;25  /&lt;br /&gt;&lt;br /&gt;Type created.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;rop@ROP92&gt; create or replace type body string_agg_type&lt;br /&gt;2  is&lt;br /&gt;3&lt;br /&gt;4  static function ODCIAggregateInitialize(sctx IN OUT string_agg_type)&lt;br /&gt;5  return number&lt;br /&gt;6  is&lt;br /&gt;7  begin&lt;br /&gt;8      sctx := string_agg_type( null );&lt;br /&gt;9      return ODCIConst.Success;&lt;br /&gt;10  end;&lt;br /&gt;11&lt;br /&gt;12  member function ODCIAggregateIterate(self IN OUT string_agg_type,&lt;br /&gt;13                                       value IN varchar2 )&lt;br /&gt;14  return number&lt;br /&gt;15  is&lt;br /&gt;16  begin&lt;br /&gt;17      self.total := self.total || ',' || value;&lt;br /&gt;18      return ODCIConst.Success;&lt;br /&gt;19  end;&lt;br /&gt;20&lt;br /&gt;21  member function ODCIAggregateTerminate(self IN string_agg_type,&lt;br /&gt;22                                         returnValue OUT varchar2,&lt;br /&gt;23                                         flags IN number)&lt;br /&gt;24  return number&lt;br /&gt;25  is&lt;br /&gt;26  begin&lt;br /&gt;27      returnValue := ltrim(self.total,',');&lt;br /&gt;28      return ODCIConst.Success;&lt;br /&gt;29  end;&lt;br /&gt;30&lt;br /&gt;31  member function ODCIAggregateMerge(self IN OUT string_agg_type,&lt;br /&gt;32                                     ctx2 IN string_agg_type)&lt;br /&gt;33  return number&lt;br /&gt;34  is&lt;br /&gt;35  begin&lt;br /&gt;36      self.total := self.total || ctx2.total;&lt;br /&gt;37      return ODCIConst.Success;&lt;br /&gt;38  end;&lt;br /&gt;39  end;&lt;br /&gt;40  /&lt;br /&gt;&lt;br /&gt;Type body created.&lt;br /&gt;&lt;br /&gt;rop@ROP92&gt;&lt;br /&gt;rop@ROP92&gt; CREATE or replace&lt;br /&gt;2  FUNCTION stragg(input varchar2 )&lt;br /&gt;3  RETURN varchar2&lt;br /&gt;4  PARALLEL_ENABLE AGGREGATE USING string_agg_type;&lt;br /&gt;5  /&lt;br /&gt;&lt;br /&gt;Function created.&lt;br /&gt;&lt;br /&gt;rop@ROP92&gt;&lt;br /&gt;rop@ROP92&gt; select deptno, stragg(ename)&lt;br /&gt;2    from scott.emp&lt;br /&gt;3   group by deptno&lt;br /&gt;4  /&lt;br /&gt;&lt;br /&gt;DEPTNO   STRAGG(ENAME)&lt;br /&gt;-------------------------------&lt;br /&gt;   10   CLARK,KING,MILLER&lt;br /&gt;   20   SMITH,FORD,ADAMS,SCOTT,JONES&lt;br /&gt;   30   ALLEN,BLAKE,MARTIN,TURNER,JAMES,WARD&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;En el ejemplo concatené los empleados en una sola fila agrupados por el departamento al cual pertenecen. Si tuvieran que concatenar mas valores y no le alcanza con varchar2 tambien pueden encontrar en el sitio de Tom una variante para usar CLOB, aunque es bastante mas lenta.&lt;br /&gt;&lt;br /&gt;Ahora bien, en 11g R2 hubo una actualizacion importante de la funciones analiticas, tambien llamadas Analytic Functions II, y una de estas nuevas funciones (LISTAGG)se puede utilizar para hacer lo mismo que stragg en forma nativa evitando tener que crear el tipo stragg y demás. Les muestro un ejemplito para listar los tipos de trabajo que hay en cada departamento:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@ROP112&gt; select deptno, listagg(job,',') within group (order by job) jobs&lt;br /&gt;2    from (select distinct deptno, job from scott.emp)&lt;br /&gt;3   group by deptno&lt;br /&gt;4   order by deptno&lt;br /&gt;5  /&lt;br /&gt;&lt;br /&gt;DEPTNO JOBS&lt;br /&gt;---------- ------------------------------&lt;br /&gt;   10 CLERK,MANAGER,PRESIDENT&lt;br /&gt;   20 ANALYST,CLERK,MANAGER&lt;br /&gt;   30 CLERK,MANAGER,SALESMAN&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Como siempre digo, es importante leer los manuales cada vez que se libera una nueva versión, sobre todo el "Oracle New Features" que es un resumen de las nuevas caracteriticas. Me sucede a menudo que veo codigos o formas de administración antiguas que insumen muchas horas para hacer lo mismo que ya esta resuelto en forma nativa o mas simple a partir de una nueva versión.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5727738329548735676-4594418691347250207?l=oramdq.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://oramdq.blogspot.com/feeds/4594418691347250207/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://oramdq.blogspot.com/2010/01/concatenar-columnas-en-11g-r2.html#comment-form' title='1 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/4594418691347250207'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/4594418691347250207'/><link rel='alternate' type='text/html' href='http://oramdq.blogspot.com/2010/01/concatenar-columnas-en-11g-r2.html' title='Concatenar valores de una columna en 11g R2 (usando la función LISTAGG)'/><author><name>Pablo A. Rovedo</name><uri>http://www.blogger.com/profile/02924687287216878757</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://3.bp.blogspot.com/_scsqOe7UOdc/SYDOV9ANLfI/AAAAAAAAAFQ/71M2XO1AZ1M/S220/DSC02067+(Small).JPG'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5727738329548735676.post-8711656842720493503</id><published>2009-12-15T10:57:00.001-08:00</published><updated>2010-01-21T06:31:59.613-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='SQL'/><title type='text'>Como encontrar los "agujeros" (gaps) en las columnas que se llenan con valores de secuencias</title><content type='html'>Voy a mostrar un ejemplo de uso de las funciones analiticas "Analytical Functions" para mostrarles de que forma sencilla y elegante se pueden encontrar los gaps de valores en las columnas alimentadas por secuencias. Es sabido que el objeto secuencia no es transaccional, por lo tanto si una transaccion se descarta (rollback) el próximo valor de secuencia obtenido para la transacción se pierde. Tambien es común que las secuencias manejen un cache y por lo tanto ante una bajada de la base de datos tambien se pierden los valores cacheados que no habian sido consumidos. A veces se necesita saber cuales son los intervalos no cosecutivos por algún tema de negocio. Para hacer eso se podría usar un bloque procedureal con pl/sql, pero con funciones analiticas lo resolvemos de una manera mas simple y performante, veamos con un ejemplo desde cero como hacerlo:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA11G&gt; create table t (x int);&lt;br /&gt;&lt;br /&gt;Tabla creada.&lt;br /&gt;&lt;br /&gt;rop@DESA11G&gt; create sequence t_seq;&lt;br /&gt;&lt;br /&gt;Secuencia creada.&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Una vez creada la tabla y la secuencia voy a llenar la tabla T con 1000 valores consecutivos:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA11G&gt; insert into t&lt;br /&gt;  2  select t_seq.nextval&lt;br /&gt;  3  from dual&lt;br /&gt;  4  connect by rownum &lt;= 1000;   &lt;br /&gt;1000 filas creadas.    &lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Ahora voy a eliminar filas para generar gaps y poder mostrar como funciona la senntencia para listar agujeros.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA11G&gt; delete from t&lt;br /&gt;  2  where (x between 100 and 150) or (x between 900 and 910);&lt;br /&gt;62 filas suprimidas.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Ya tengo armado el ambiente asi que ahora simplemente ejecuto la sentencia&lt;br /&gt;con FA para detectar los gaps:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;rop@DESA11G&gt; select *&lt;br /&gt;  2  from (select unique x,&lt;br /&gt;  3               lead(x) over (order by x) x_next&lt;br /&gt;  4        from t)&lt;br /&gt;  5  where x+1 != x_next;&lt;br /&gt;&lt;br /&gt;         X     X_NEXT&lt;br /&gt;---------- ----------&lt;br /&gt;        99        151&lt;br /&gt;       899        911&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Se puede observar que se listaron los intervalos no cosecutivos que corresponden a las filas que se eliminaron en el ejemplo.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5727738329548735676-8711656842720493503?l=oramdq.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://oramdq.blogspot.com/feeds/8711656842720493503/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://oramdq.blogspot.com/2009/12/como-encontrar-los-agujeros-gaps-en-las.html#comment-form' title='2 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/8711656842720493503'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/8711656842720493503'/><link rel='alternate' type='text/html' href='http://oramdq.blogspot.com/2009/12/como-encontrar-los-agujeros-gaps-en-las.html' title='Como encontrar los &quot;agujeros&quot; (gaps) en las columnas que se llenan con valores de secuencias'/><author><name>Pablo A. Rovedo</name><uri>http://www.blogger.com/profile/02924687287216878757</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://3.bp.blogspot.com/_scsqOe7UOdc/SYDOV9ANLfI/AAAAAAAAAFQ/71M2XO1AZ1M/S220/DSC02067+(Small).JPG'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5727738329548735676.post-5282686979077162454</id><published>2009-12-11T09:57:00.000-08:00</published><updated>2010-04-16T04:47:55.876-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Diagnóstico'/><category scheme='http://www.blogger.com/atom/ns#' term='Scripts'/><title type='text'>Reporte para analizar planes que referencien a una tabla dada</title><content type='html'>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.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;set serverout on&lt;br /&gt;set line 120&lt;br /&gt;set pagesize 9999&lt;br /&gt;set verify off&lt;br /&gt;set feed off&lt;br /&gt;&lt;br /&gt;ACCEPT tabla PROMPT "Ingrese Tabla a Analizar: "&lt;br /&gt;PROMPT&lt;br /&gt;PROMPT&lt;br /&gt;&lt;br /&gt;begin&lt;br /&gt;    for i in (select sp.sql_id,max(sh.begin_interval_time) ufecha&lt;br /&gt;              from dba_hist_sql_plan sp,&lt;br /&gt;                   dba_hist_sqlstat ss,&lt;br /&gt;                   dba_hist_snapshot sh&lt;br /&gt;              where sp.sql_id = ss.sql_id&lt;br /&gt;                and ss.snap_id = sh.snap_id&lt;br /&gt;                and sp.object_name = upper('&amp;amp;tabla')&lt;br /&gt;              group by sp.sql_id)&lt;br /&gt;    loop&lt;br /&gt;       dbms_output.put_line('Ultima Ejecución: '||to_char(i.ufecha,'DD/MM/YYYY HH24:MI'));&lt;br /&gt;       for j in (select * from table(dbms_xplan.display_awr(i.sql_id)))&lt;br /&gt;       loop&lt;br /&gt;           dbms_output.put_line (j.plan_table_output);&lt;br /&gt;       end loop;&lt;br /&gt;       dbms_output.put_line(chr(10)||rpad('*',100,'*')||chr(10));&lt;br /&gt;    end loop;&lt;br /&gt;end;&lt;br /&gt;/&lt;br /&gt;&lt;br /&gt;set verify on&lt;br /&gt;set feed on&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5727738329548735676-5282686979077162454?l=oramdq.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://oramdq.blogspot.com/feeds/5282686979077162454/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://oramdq.blogspot.com/2009/12/reporte-para-analizar-planes-que.html#comment-form' title='4 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/5282686979077162454'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/5282686979077162454'/><link rel='alternate' type='text/html' href='http://oramdq.blogspot.com/2009/12/reporte-para-analizar-planes-que.html' title='Reporte para analizar planes que referencien a una tabla dada'/><author><name>Pablo A. Rovedo</name><uri>http://www.blogger.com/profile/02924687287216878757</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://3.bp.blogspot.com/_scsqOe7UOdc/SYDOV9ANLfI/AAAAAAAAAFQ/71M2XO1AZ1M/S220/DSC02067+(Small).JPG'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5727738329548735676.post-4103656814960425537</id><published>2009-12-09T06:20:00.001-08:00</published><updated>2009-12-10T07:13:11.416-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='CBO'/><category scheme='http://www.blogger.com/atom/ns#' term='Performance'/><title type='text'>SQL Profiles. Una ayuda adicional para que el optimizador elija el plan correcto.</title><content type='html'>Se puede recolectar estadísticas sobre tablas (nro de filas,tamaño promedio de fila,bloques, etc), sobre indíces (clustering factor, cantidad de hojas, altura,etc), sobre columnas (histogramas, densidad, valores distintos,etc) sobre el sistema (velocidad de cpu,tasa de transferencia a disco,etc) y tambien sobre la tablas de catálogo. Toda esta información permite generar el acceso a los datos mas adecuado (armar el plan correcto).De todas formas hay ciertos casos donde no es suficiente toda la estadistica recolectada como cuando se invocan funciones en la sentencias sql o cuando existe una fuerte correlación entre columnas que se filtran en un predicado.&lt;br /&gt;&lt;br /&gt;A partir de 10g existen los SQL Profiles que corrigen estos problemas aportandole al optimizador mayor información, hay que pensarlo como estadisticas particulares sobre las sentencias. En notas anteriores mostré las caracteristicas de STORED_OUTLINES(8i) y de las SQL BASELINES (11g). En esta nota con SQL PROFILES(10g) , en orden no cronólogico, se completa la trilogia de mecanismos de corrección de planes que se han ido agregando versión tras versión. Este conjunto de herramientas se podrían pensar como mecanismos de hinteo encubierto que han ido evolucionando y sofisticandose con cada nuevo realease de base.&lt;br /&gt;&lt;br /&gt;A diferencia de los STORED_OUTLINES que fijan un determinado plan a una sentencia, los SQL PROFILES son un set de hints que corrigen ciertos calculos internos del optimizador para que no se desvien los planes.&lt;br /&gt;Para obtener los profiles se puede usar el paquete DBMS_SQLTUNE que crea un tarea y realiza una pseudo ejecución tomando métricas que le permiten conocer mayor detalle y analizar las correlaciones entre las columnas referenciadas en los predicados de la sentencia.&lt;br /&gt;&lt;br /&gt;En el ejemplo que armé, cree una tabla TBL_AUTOS que podría representar datos de un concesionario o fabrica automotriz, con 3 campos: un identificación unica del auto (ID), una categoria o segmento: (A) Segmento Bajo, (B) Segmento Medio, (C)) Segmento Alto y (D) Gama Premium y como tercer campo el precio o valor del vehiculo en dolares, es una aproximación de los valores aqui en Argentina. Ahora voy a crear la tabla tratando de generar en forma ficticia una distribución cercana a una distribución real.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@ROP102&gt; create table tbl_autos (id int,categoria char(1),valor int);&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Tabla creada.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;rop@ROP102&gt; create sequence seq_autos;&lt;br /&gt;&lt;br /&gt;Secuencia creada.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;rop@ROP102&gt; insert into tbl_autos&lt;br /&gt;2 select seq_autos.nextval,&lt;br /&gt;3 'A',&lt;br /&gt;4 trunc(dbms_random.value(8000,12000))&lt;br /&gt;5 from dual&lt;br /&gt;6 connect by rownum &lt;= 60000;&lt;br /&gt;&lt;br /&gt;60000 filas creadas.&lt;br /&gt;&lt;br /&gt;rop@ROP102&gt; insert into tbl_autos&lt;br /&gt;2 select seq_autos.nextval,&lt;br /&gt;3 'B',&lt;br /&gt;4 trunc(dbms_random.value(12000,15000))&lt;br /&gt;5 from dual&lt;br /&gt;6 connect by rownum &lt;= 25000;&lt;br /&gt;&lt;br /&gt;25000 filas creadas.&lt;br /&gt;&lt;br /&gt; rop@ROP102&gt; insert into tbl_autos&lt;br /&gt;2 select seq_autos.nextval,&lt;br /&gt;3 'C',&lt;br /&gt;4 trunc(dbms_random.value(15000,30000))&lt;br /&gt;5 from dual&lt;br /&gt;6 connect by rownum &lt;= 14500;&lt;br /&gt;&lt;br /&gt;14500 filas creadas.&lt;br /&gt;&lt;br /&gt;rop@ROP102&gt; insert into tbl_autos&lt;br /&gt;2 select seq_autos.nextval,&lt;br /&gt;3 'D',&lt;br /&gt;4 trunc(dbms_random.value(40000,80000))&lt;br /&gt;5 from dual&lt;br /&gt;6 connect by rownum &lt;= 500 ;&lt;br /&gt;&lt;br /&gt;500 filas creadas.&lt;br /&gt;&lt;br /&gt;rop@ROP102&gt; create index autos_idx on tbl_autos (categoria,valor);&lt;br /&gt;&lt;br /&gt;Índice creado.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;rop@ROP102&gt; begin&lt;br /&gt;2 dbms_stats.gather_table_stats(ownname =&gt; user,&lt;br /&gt;tabname =&gt; 'TBL_AUTOS',&lt;br /&gt;cascade =&gt;true);&lt;br /&gt;3 end;&lt;br /&gt;4 /&lt;br /&gt;&lt;br /&gt;Procedimiento PL/SQL terminado correctamente.&lt;br /&gt;&lt;br /&gt;Con los pasos listado arriba tengo una tabla TBL_AUTOS, indexada por (categoria,valor) y con las estadísticas recolectadas.&lt;br /&gt;&lt;br /&gt;Los vehiculos de categoria 'A' nunca superan los 12000 dólares entonces es de esperar que si realizo una consulta filtrando por categoria='A' y valor &gt; 20000, el resultado debería ser 0, verdad?. Veamos que pasa.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@ROP102&gt; explain plan for  select min(id)&lt;br /&gt;  2    from tbl_autos&lt;br /&gt;  3    where categoria = 'A'&lt;br /&gt;  4      and valor &gt; 20000;&lt;br /&gt;&lt;br /&gt;Explicado.&lt;br /&gt;&lt;br /&gt;rop@ROP102&gt; select * from table(dbms_xplan.display);&lt;br /&gt;&lt;br /&gt;PLAN_TABLE_OUTPUT&lt;br /&gt;----------------------------------------------------------------------------------------------------&lt;br /&gt;Plan hash value: 649483413&lt;br /&gt;&lt;br /&gt;--------------------------------------------------------------------------------&lt;br /&gt; Id   Operation           Name       Rows   Bytes  Cost (%CPU) Time    &lt;br /&gt;--------------------------------------------------------------------------------&lt;br /&gt;   0  SELECT STATEMENT                   1     11     73   (7) 00:00:01&lt;br /&gt;   1   SORT AGGREGATE                    1     11                      &lt;br /&gt;*  2    TABLE ACCESS FULL TBL_AUTOS  20832    223K    73   (7) 00:00:01&lt;br /&gt;--------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;   2 - filter("VALOR"&gt;20000 AND "CATEGORIA"='A')&lt;br /&gt;&lt;br /&gt;14 filas seleccionadas.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Fijense que se estimaron 20832 filas, lo cual dista muchisimo de la realidad. Al calcular tan mal la cardinalidad el optimizador optó por un plan inadecuado, dictando recorrer toda la tabla para obtener el resultado (FULL SCAN), suena ilógico, no?. Esto se da por lo que comenté antes respecto a que el optimizador no cuenta con información de distribución correlacionada o multi-columna.&lt;br /&gt;&lt;br /&gt;Voy a ejecutar el optimizador automático o SQL TUNING ADVISOR para ver como puedo evitar que se infiera un plan sub-optimo.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@ROP102&gt; declare&lt;br /&gt;  2     l_task_id     varchar2(20);&lt;br /&gt;  3  begin&lt;br /&gt;  4      l_task_id := dbms_sqltune.create_tuning_task (&lt;br /&gt;  5        sql_text  =&gt; 'select min(id)&lt;br /&gt;  6                      from tbl_autos&lt;br /&gt;  7                      where categoria = ''A''&lt;br /&gt;  8                        and valor &gt; 20000',&lt;br /&gt;  9        user_name  =&gt; 'ROP',&lt;br /&gt; 10        scope      =&gt; 'COMPREHENSIVE',&lt;br /&gt; 11        time_limit =&gt; 120,&lt;br /&gt; 12        task_name  =&gt; 'test'&lt;br /&gt; 13     );&lt;br /&gt; 14     dbms_sqltune.execute_tuning_task ('test');&lt;br /&gt; 15  end;&lt;br /&gt; 16  /&lt;br /&gt;&lt;br /&gt;Procedimiento PL/SQL terminado correctamente.&lt;br /&gt;&lt;br /&gt;rop@ROP102&gt; set serveroutput on size 999999&lt;br /&gt;rop@ROP102&gt; set long 999999&lt;br /&gt;rop@ROP102&gt; select dbms_sqltune.report_tuning_task ('test') from dual;&lt;br /&gt;&lt;br /&gt;DBMS_SQLTUNE.REPORT_TUNING_TASK('TEST')&lt;br /&gt;--------------------------------------------------------------------------------&lt;br /&gt;GENERAL INFORMATION SECTION&lt;br /&gt;-------------------------------------------------------------------------------&lt;br /&gt;Tuning Task Name   : test&lt;br /&gt;Tuning Task Owner  : ROP&lt;br /&gt;Workload Type      : Single SQL Statement&lt;br /&gt;Scope              : COMPREHENSIVE&lt;br /&gt;Time Limit(seconds): 120&lt;br /&gt;Completion Status  : COMPLETED&lt;br /&gt;Started at         : 12/07/2009 14:41:01&lt;br /&gt;Completed at       : 12/07/2009 14:41:01&lt;br /&gt;&lt;br /&gt;-------------------------------------------------------------------------------&lt;br /&gt;Schema Name: ROP&lt;br /&gt;SQL ID     : 7b5twtc2yrgsy&lt;br /&gt;SQL Text   : select min(id)&lt;br /&gt;                                 from tbl_autos&lt;br /&gt;                                 where categoria = 'A'&lt;br /&gt;                                   and valor &gt; 20000&lt;br /&gt;&lt;br /&gt;-------------------------------------------------------------------------------&lt;br /&gt;FINDINGS SECTION (1 finding)&lt;br /&gt;-------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;1- SQL Profile Finding (see explain plans section below)&lt;br /&gt;--------------------------------------------------------&lt;br /&gt;  Se ha encontrado un plan de ejecución potencialmente mejor para esta&lt;br /&gt;  sentencia.&lt;br /&gt;&lt;br /&gt;  Recommendation (estimated benefit: 99.19%)&lt;br /&gt;  ------------------------------------------&lt;br /&gt;  - Puede aceptar el perfil SQL recomendado.&lt;br /&gt;    execute dbms_sqltune.accept_sql_profile(task_name =&gt; 'test', task_owner&lt;br /&gt;            =&gt; 'ROP', replace =&gt; TRUE);&lt;br /&gt;&lt;br /&gt;  Validation results&lt;br /&gt;  ------------------&lt;br /&gt;  Se ha probado SQL profile  ejecutando su plan y el plan original y midiendo&lt;br /&gt;  sus respectivas estadísticas de ejecución. Puede que uno de los planes se&lt;br /&gt;  haya ejecutado sólo parcialmente si el otro se ha ejecutado por completo en&lt;br /&gt;  menos tiempo.r&lt;br /&gt;&lt;br /&gt;                           Original Plan  With SQL Profile  % Improved&lt;br /&gt;                           -------------  ----------------  ----------&lt;br /&gt;  Completion Status:            COMPLETE          COMPLETE&lt;br /&gt;  Elapsed Time(ms):                   19                 0        100%&lt;br /&gt;  CPU Time(ms):                       10                 0        100%&lt;br /&gt;  User I/O Time(ms):                   0                 0&lt;br /&gt;  Buffer Gets:                       248                 2      99.19%&lt;br /&gt;  Disk Reads:                          0                 0&lt;br /&gt;  Direct Writes:                       0                 0&lt;br /&gt;  Rows Processed:                      1                 1&lt;br /&gt;  Fetches:                             1                 1&lt;br /&gt;  Executions:                          1                 1&lt;br /&gt;&lt;br /&gt;-------------------------------------------------------------------------------&lt;br /&gt;EXPLAIN PLANS SECTION&lt;br /&gt;-------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;1- Original With Adjusted Cost&lt;br /&gt;------------------------------&lt;br /&gt;Plan hash value: 649483413&lt;br /&gt;&lt;br /&gt;--------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt; Id   Operation           Name       Rows   Bytes  Cost (%CPU) Time    &lt;br /&gt;&lt;br /&gt;--------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;   0  SELECT STATEMENT                   1     11     73   (7) 00:00:01&lt;br /&gt;&lt;br /&gt;   1   SORT AGGREGATE                    1     11                      &lt;br /&gt;&lt;br /&gt;*  2    TABLE ACCESS FULL TBL_AUTOS      1     11     73   (7) 00:00:01&lt;br /&gt;&lt;br /&gt;--------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;   2 - filter("VALOR"&gt;20000 AND "CATEGORIA"='A')&lt;br /&gt;&lt;br /&gt;2- Original With Adjusted Cost&lt;br /&gt;------------------------------&lt;br /&gt;Plan hash value: 649483413&lt;br /&gt;&lt;br /&gt;--------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt; Id   Operation           Name       Rows   Bytes  Cost (%CPU) Time    &lt;br /&gt;&lt;br /&gt;--------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;   0  SELECT STATEMENT                   1     11     73   (7) 00:00:01&lt;br /&gt;&lt;br /&gt;   1   SORT AGGREGATE                    1     11                      &lt;br /&gt;&lt;br /&gt;*  2    TABLE ACCESS FULL TBL_AUTOS      1     11     73   (7) 00:00:01&lt;br /&gt;&lt;br /&gt;--------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;   2 - filter("VALOR"&gt;20000 AND "CATEGORIA"='A')&lt;br /&gt;&lt;br /&gt;3- Using SQL Profile&lt;br /&gt;--------------------&lt;br /&gt;Plan hash value: 552074332&lt;br /&gt;&lt;br /&gt;--------------------------------------------------------------------------------&lt;br /&gt;----------&lt;br /&gt; Id   Operation                     Name       Rows   Bytes  Cost (%CPU)&lt;br /&gt;Time    &lt;br /&gt;--------------------------------------------------------------------------------&lt;br /&gt;----------&lt;br /&gt;   0  SELECT STATEMENT                             1     11      3   (0)&lt;br /&gt;00:00:01&lt;br /&gt;   1   SORT AGGREGATE                              1     11            &lt;br /&gt;        &lt;br /&gt;   2    TABLE ACCESS BY INDEX ROWID TBL_AUTOS      1     11      3   (0)&lt;br /&gt;00:00:01&lt;br /&gt;*  3     INDEX RANGE SCAN           AUTOS_IDX      1             2   (0)&lt;br /&gt;00:00:01&lt;br /&gt;--------------------------------------------------------------------------------&lt;br /&gt;----------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;   3 - access("CATEGORIA"='A' AND "VALOR"&gt;20000 AND "VALOR" IS NOT NULL)&lt;br /&gt;&lt;br /&gt;-------------------------------------------------------------------------------&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;El analizador automático sugiere aplicar un sql profile y estima una mejora de casi el 100%. Apliquemos el profile y veamos como resuelve la sentencia con esa información adicional.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;  1  begin&lt;br /&gt;  2     dbms_sqltune.accept_sql_profile(task_name =&gt; 'test',&lt;br /&gt;  3                                     task_owner =&gt; 'ROP',&lt;br /&gt;  4                                     replace =&gt; TRUE);&lt;br /&gt;  5* end;&lt;br /&gt;rop@ROP102&gt; /&lt;br /&gt;&lt;br /&gt;Procedimiento PL/SQL terminado correctamente.&lt;br /&gt;&lt;br /&gt;rop@ROP102&gt; explain plan for   select min(id)&lt;br /&gt;  2    from tbl_autos&lt;br /&gt;  3    where categoria = 'A'&lt;br /&gt;  4      and valor &gt; 20000;&lt;br /&gt;&lt;br /&gt;Explicado.&lt;br /&gt;&lt;br /&gt;rop@ROP102&gt; select * from table(dbms_xplan.display);&lt;br /&gt;&lt;br /&gt;PLAN_TABLE_OUTPUT&lt;br /&gt;------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;Plan hash value: 552074332&lt;br /&gt;&lt;br /&gt;------------------------------------------------------------------------------------------&lt;br /&gt; Id   Operation                     Name       Rows   Bytes  Cost (%CPU) Time    &lt;br /&gt;------------------------------------------------------------------------------------------&lt;br /&gt;   0  SELECT STATEMENT                             1     11      3   (0) 00:00:01&lt;br /&gt;   1   SORT AGGREGATE                              1     11                      &lt;br /&gt;   2    TABLE ACCESS BY INDEX ROWID TBL_AUTOS      1     11      3   (0) 00:00:01&lt;br /&gt;*  3     INDEX RANGE SCAN           AUTOS_IDX      1             2   (0) 00:00:01&lt;br /&gt;------------------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;   3 - access("CATEGORIA"='A' AND "VALOR"&gt;20000 AND "VALOR" IS NOT NULL)&lt;br /&gt;&lt;br /&gt;Note&lt;br /&gt;-----&lt;br /&gt;  &lt;span style="FONT-WEIGHT: bold"&gt; - SQL profile "SYS_SQLPROF_01256a3d43600000" used for this statement&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Se puede observar el el optimizador ahora utilizó el profile "SYS_SQLPROF_01256a3d43600000" estimando ahora una cardinalidad de 1 (debería ser 0 (cero) pero nunca se muestra 0 a menos que se trate de un predicado con una contradicción, por ejemplo: 1=2).&lt;br /&gt;&lt;br /&gt;A partir de 11g existen las denominadas "extended statistics" que nos permiten recolectar estadísticas del tipo function based y multi-column y minimizar los problemas de estimación de cardinalidad con filtros de columna correlacionadas.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5727738329548735676-4103656814960425537?l=oramdq.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://oramdq.blogspot.com/feeds/4103656814960425537/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://oramdq.blogspot.com/2009/12/sql-profiles-una-ayuda-para-que-el.html#comment-form' title='4 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/4103656814960425537'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/4103656814960425537'/><link rel='alternate' type='text/html' href='http://oramdq.blogspot.com/2009/12/sql-profiles-una-ayuda-para-que-el.html' title='SQL Profiles. Una ayuda adicional para que el optimizador elija el plan correcto.'/><author><name>Pablo A. Rovedo</name><uri>http://www.blogger.com/profile/02924687287216878757</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://3.bp.blogspot.com/_scsqOe7UOdc/SYDOV9ANLfI/AAAAAAAAAFQ/71M2XO1AZ1M/S220/DSC02067+(Small).JPG'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5727738329548735676.post-3803977271290465772</id><published>2009-12-04T05:18:00.001-08:00</published><updated>2010-10-08T11:29:13.755-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Conceptos'/><category scheme='http://www.blogger.com/atom/ns#' term='CBO'/><category scheme='http://www.blogger.com/atom/ns#' term='Performance'/><title type='text'>Una introducción a los histogramas. Para que se usan y como se interpretan</title><content type='html'>Si alguna vez se pusieron a buscar información sobre que son, como funcionan y para que sirven realmente los histogramas en Oracle se habrán percatado que no existe demasiada data al respecto. La mayoria de la información es solo de referencia y no se explica claramente la verdadera esencia. El tema es bastante extenso, en esta primera parte, mi idea es introducir los conceptos principales, como se guarda la información de histogramas en el catálogo y como se interpreta dicho contenido. En futuras notas voy a mostrarles mas detalle de como se calcula la cardinalidad y el costo de los planes en base a la información estadística y de histogramas y tambien en que casos no sirven. &lt;br /&gt;&lt;br /&gt;Oracle utiliza los histogramas para mejorar los calculos de cardinalidad y selectividad cuando la distribución de los datos no es uniforme. Hay que pensar a los histogramas como una "dibujo" de los datos que representa la distribución del contenido en las columnas. Existen dos tipos de histogramas: "Frecuency Histograms" y "Height Balanced Histrograms". Los primeros se usan cuando los valores distintos de la columna son pocos, y los segundos cuando la columna tiene gran cantidad de valores diferentes. &lt;br /&gt;&lt;br /&gt;En los histogramas por frecuencia cada valor de la columna se corresponde con una entrada del histograma. Cada entrada contiene en número de concurrencia para un valor. En los histogramas balanceados la columna es dividida en buckets (barras), cada barra tiene el mismo número de filas y cada valor puede ser representado por uno o mas buckets, según su popularidad (cantidad de ocurrencias).&lt;br /&gt;&lt;br /&gt;La principal tarea de los histogramas es ayudar a obtener la cardinalidad correcta, como ya comenté en otras notas, el calculo de la cardinalidad es fundamental para que Oracle arme el plan mas adecuado, lo que implica elegir el método y el orden de los joins y la elección de los indices correctos, y asi garantizar la mejor performance posible. &lt;br /&gt;&lt;br /&gt;La forma de almacenamiento sigue conceptos estadísticos como percentiles y cuartiles y los valores se agrupan en buckets (si se grafica un histogramas, los buckets son como las barras en un gráfico de barras común y corriente). &lt;br /&gt;&lt;br /&gt;Ahora vamos a hacer un par de pruebas y asi tratar de entender un poco mas el concepto detrás de los histogramas.&lt;br /&gt;&lt;br /&gt;Primero voy a crear una tabla que tendrá 11110 filas, con cuatro valores posibles (1,2,3 y 4) de forma tal de que cada valor tenga una cantidad redonda de registros (potencia de 10).&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;rop@DESA10G&gt; create table t&lt;br /&gt;  2  as select case when (rownum between 1 and 10) then 1&lt;br /&gt;  3                 when (rownum between 11 and 110) then 2&lt;br /&gt;  4                 when (rownum between 111 and 1110) then 3&lt;br /&gt;  5                 when (rownum between 1111 and 11110) then 4&lt;br /&gt;  6            end x&lt;br /&gt;  7  from dual&lt;br /&gt;  8  connect by rownum &lt;= 11110;&lt;br /&gt;&lt;br /&gt;Tabla creada.&lt;br /&gt;&lt;br /&gt;rop@DESA10G&gt; select x,count(1)&lt;br /&gt;  2          from t&lt;br /&gt;  3          group by x;&lt;br /&gt;&lt;br /&gt;         X   COUNT(1)&lt;br /&gt;---------- ----------&lt;br /&gt;         1         10&lt;br /&gt;         2        100&lt;br /&gt;         3       1000&lt;br /&gt;         4      10000&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;La tabla T nos quedó con la distribución que se muestra arriba. Voy a recolectar&lt;br /&gt;estadísticas y ver en la tabla de catálogo USER_TAB_COL_STATISTICS los datos estadisticos de la columna X:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA10G&gt; begin &lt;br /&gt;  2      dbms_stats.gather_table_stats(ownname =&gt; user,tabname =&gt; 'T');&lt;br /&gt;  3  end;&lt;br /&gt;  4  /&lt;br /&gt;&lt;br /&gt;Procedimiento PL/SQL terminado correctamente.&lt;br /&gt;&lt;br /&gt;rop@DESA10G&gt; exec print_table('select * from user_tab_col_statistics where table_name = ''T''');&lt;br /&gt;TABLE_NAME                    : T&lt;br /&gt;COLUMN_NAME                   : X&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;NUM_DISTINCT                  : 4&lt;/span&gt;&lt;br /&gt;LOW_VALUE                     : C102&lt;br /&gt;HIGH_VALUE                    : C105&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;DENSITY                       : .25&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;NUM_NULLS                     : 0&lt;br /&gt;NUM_BUCKETS                   : 1&lt;/span&gt;&lt;br /&gt;LAST_ANALYZED                 : 02-dic-2009 15:28:59&lt;br /&gt;SAMPLE_SIZE                   : 11110&lt;br /&gt;GLOBAL_STATS                  : YES&lt;br /&gt;USER_STATS                    : NO&lt;br /&gt;AVG_COL_LEN                   : 3&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;HISTOGRAM                     : NONE&lt;/span&gt;&lt;br /&gt;-----------------&lt;br /&gt;&lt;br /&gt;Procedimiento PL/SQL terminado correctamente.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;De los datos mostrados arriba vemos que Oracle detectó 4 valores distintos, que no hay valores nulos, que la densidad es 0.25 (1/"cant. de valores distinto"=1/4),que hay un solo bucket y que no hay histogramas (NONE). Ya que en la recolección no explicitamos el parámetro method_opt, que da directivas de como armar los histogramas, se uso el default que es: "for all columns size auto". La unica información de distribución es la siguiente:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA10G&gt; select * from user_tab_histograms where table_name = 'T';&lt;br /&gt;&lt;br /&gt;TABLE_NAME COLUMN_NAM ENDPOINT_NUMBER ENDPOINT_VALUE ENDPOINT_A&lt;br /&gt;---------- ---------- --------------- -------------- ----------&lt;br /&gt;T          X                       0              1&lt;br /&gt;T          X                       1              4&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Recolectando estadísticas diciendole a Oracle que use 4 buckets para representar la distribución obtenemos:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA10G&gt; begin &lt;br /&gt;  2      dbms_stats.gather_table_stats(ownname =&gt; user,&lt;br /&gt;  3                                    tabname =&gt; 'T',&lt;br /&gt;  4                                    method_opt=&gt;'for all columns size 4');&lt;br /&gt;  5  end;&lt;br /&gt;  6  /&lt;br /&gt;&lt;br /&gt;Procedimiento PL/SQL terminado correctamente.&lt;br /&gt;&lt;br /&gt;rop@DESA10G&gt; exec print_table('select * from user_tab_col_statistics where table_name = ''T''');&lt;br /&gt;TABLE_NAME                    : T&lt;br /&gt;COLUMN_NAME                   : X&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;NUM_DISTINCT                  : 4&lt;/span&gt;&lt;br /&gt;LOW_VALUE                     : C102&lt;br /&gt;HIGH_VALUE                    : C105&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;DENSITY                       : .000045004500450045&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;NUM_NULLS                     : 0&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;NUM_BUCKETS                   : 4&lt;/span&gt;&lt;br /&gt;LAST_ANALYZED                 : 02-dic-2009 16:12:21&lt;br /&gt;SAMPLE_SIZE                   : 11110&lt;br /&gt;GLOBAL_STATS                  : YES&lt;br /&gt;USER_STATS                    : NO&lt;br /&gt;AVG_COL_LEN                   : 3&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;HISTOGRAM                     : FREQUENCY&lt;/span&gt;&lt;br /&gt;-----------------&lt;br /&gt;&lt;br /&gt;Procedimiento PL/SQL terminado correctamente.&lt;br /&gt;&lt;br /&gt;rop@DESA10G&gt; select * from user_tab_histograms where table_name = 'T';&lt;br /&gt;&lt;br /&gt;TABLE_NAME COLUMN_NAM ENDPOINT_NUMBER ENDPOINT_VALUE ENDPOINT_A&lt;br /&gt;---------- ---------- --------------- -------------- ----------&lt;br /&gt;T          X                       10              1&lt;br /&gt;T          X                      110              2&lt;br /&gt;T          X                     1110              3&lt;br /&gt;T          X                    11110              4&lt;br /&gt;rop@DESA10G&gt; &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Notemos que ahora se creo un "Frecuency Histrogram", que la densidad es mucho menor y viendo la tabla USER_TAB_HISTOGRAMS ahora se representa exactamente la distribución. Para interpretar esa tabla pensemos que la columna ENDPOINT_VALUE es cada valor posible de la columna y el ENDPOINT_NUMBER es la cantidad de registros iguales. Para 1 tenemos 10, para 2 tenemos 110-10=100, para 3 tenemos 1110-110=1000 y asi siguiendo.&lt;br /&gt;&lt;br /&gt;Ahora voy a armar una distribución distinta, con muchos valores distintos para mostrarles el otro tipo de histograma:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA10G&gt;  drop table t;&lt;br /&gt;&lt;br /&gt;Tabla borrada.&lt;br /&gt;&lt;br /&gt;rop@DESA10G&gt; ed&lt;br /&gt;Escrito file afiedt.buf&lt;br /&gt;&lt;br /&gt;  1  create table t&lt;br /&gt;  2  as select rownum x&lt;br /&gt;  3  from dual&lt;br /&gt;  4* connect by rownum &lt;= 11110&lt;br /&gt;rop@DESA10G&gt; /&lt;br /&gt;&lt;br /&gt;Tabla creada.&lt;br /&gt;&lt;br /&gt;rop@DESA10G&gt;  update t set x = 1 where rownum &lt;= 5000;&lt;br /&gt;&lt;br /&gt;5000 filas actualizadas.&lt;br /&gt;&lt;br /&gt;rop@DESA10G&gt; update t set x = 2 where rownum &lt;= 3000 and x != 1;&lt;br /&gt;&lt;br /&gt;3000 filas actualizadas.&lt;br /&gt;&lt;br /&gt;rop@DESA10G&gt; create index t_idx on t(x);&lt;br /&gt;&lt;br /&gt;Índice creado.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Se creó una tabla con la misma cantidad de filas que el ejemplo anterior pero ahora tenemos una distribución muy diferente. Para X=1 tenemos 5000 valores, para x=2 hay 3000 filas y para las demas solo 1 ocurrencia. Como ahora tengo muchos valores distintos si recolecto sin especificar me va a crear el histograma. Para evitar que lo cree en el parametro method_opt le especifiqué "for columns size 1" para que no asocie ningun histograma.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA10G&gt; begin&lt;br /&gt;  2      dbms_stats.gather_table_stats(ownname =&gt; user,&lt;br /&gt;  3                                    tabname =&gt; 'T',&lt;br /&gt;  4                                    cascade =&gt; true,&lt;br /&gt;  5                                    method_opt =&gt; 'for columns size 1');&lt;br /&gt;  6  end;&lt;br /&gt;  7  /&lt;br /&gt;&lt;br /&gt;Procedimiento PL/SQL terminado correctamente.&lt;br /&gt;&lt;br /&gt;rop@DESA10G&gt; exec print_table('select * from user_tab_col_statistics where table_name = ''T''');&lt;br /&gt;&lt;br /&gt;Procedimiento PL/SQL terminado correctamente.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Como se ve, consultando la tabla USER_TAB_COL_STATISTICS no obtenemos ningua fila, es decir no tenemos información de histogramas.&lt;br /&gt;Recordemos que para X=1 tenemos 5000 valores, es decir casi la mitad del total de filas, por lo tanto si filtramos en el predicado por dicho valor es de esperar que el optimizador use un full_scan, no?, veamos que pasa:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA10G&gt;  explain plan for select count(1) from t where x = 1;&lt;br /&gt;&lt;br /&gt;Explicado.&lt;br /&gt;&lt;br /&gt;rop@DESA10G&gt; select * from table(dbms_xplan.display);&lt;br /&gt;&lt;br /&gt;PLAN_TABLE_OUTPUT&lt;br /&gt;----------------------------------------------------------------------------------------------------&lt;br /&gt;Plan hash value: 3482591947&lt;br /&gt;&lt;br /&gt;---------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation         | Name  | Rows  | Bytes | Cost (%CPU)| Time     |&lt;br /&gt;---------------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT  |       |     1 |     3 |     1   (0)| 00:00:01 |&lt;br /&gt;|   1 |  SORT AGGREGATE   |       |     1 |     3 |            |          |&lt;br /&gt;|*  2 |   &lt;span style="font-weight:bold;"&gt;INDEX RANGE SCAN&lt;/span&gt;| T_IDX |   111 |   333 |     1   (0)| 00:00:01 |&lt;br /&gt;---------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;   2 - access("X"=1)&lt;br /&gt;&lt;br /&gt;14 filas seleccionadas.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Notar que el optimizador estimó (columna ROWS) 111 filas, lo cual dista mucho de la realidad, recordemos que tenemos 5000 filas que cumplen con X=1. Si recolectamos usando el método default:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA10G&gt; ed&lt;br /&gt;Escrito file afiedt.buf&lt;br /&gt;&lt;br /&gt;  1  begin&lt;br /&gt;  2      dbms_stats.gather_table_stats(ownname =&gt; user,&lt;br /&gt;  3                                    tabname =&gt; 'T',&lt;br /&gt;  4                                    cascade =&gt; true);&lt;br /&gt;  5* end;&lt;br /&gt;rop@DESA10G&gt; /&lt;br /&gt;&lt;br /&gt;Procedimiento PL/SQL terminado correctamente.&lt;br /&gt;&lt;br /&gt;rop@DESA10G&gt; exec print_table('select * from user_tab_col_statistics where table_name = ''T''');&lt;br /&gt;TABLE_NAME                    : T&lt;br /&gt;COLUMN_NAME                   : X&lt;br /&gt;NUM_DISTINCT                  : 3112&lt;br /&gt;LOW_VALUE                     : C102&lt;br /&gt;HIGH_VALUE                    : C3020C0B&lt;br /&gt;DENSITY                       : .00009000900090009&lt;br /&gt;NUM_NULLS                     : 0&lt;br /&gt;NUM_BUCKETS                   : 254&lt;br /&gt;LAST_ANALYZED                 : 04-dic-2009 10:02:53&lt;br /&gt;SAMPLE_SIZE                   : 11110&lt;br /&gt;GLOBAL_STATS                  : YES&lt;br /&gt;USER_STATS                    : NO&lt;br /&gt;AVG_COL_LEN                   : 4&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;HISTOGRAM                     : HEIGHT BALANCED&lt;/span&gt;&lt;br /&gt;-----------------&lt;br /&gt;&lt;br /&gt;Procedimiento PL/SQL terminado correctamente.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;El tipo de histograma ahora es: "HEIGHT BALANCED", la cantidad de filas distintas es 3312 y el nro de buckets es de 254 (el máximo posible)&lt;br /&gt;&lt;br /&gt;La representación de los datos en la tabla T ahora es la siguiente:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA10G&gt; select * from user_tab_histograms where table_name = 'T';&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;TABLE_NAME                     COLUMN_NAM ENDPOINT_NUMBER ENDPOINT_VALUE ENDPOINT&lt;br /&gt;------------------------------ ---------- --------------- -------------- --------&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;T                              X                      113              1&lt;br /&gt;T                              X                      181              2&lt;/span&gt;&lt;br /&gt;T                              X                      182           8008&lt;br /&gt;T                              X                      183           8052&lt;br /&gt;T                              X                      184           8096&lt;br /&gt;T                              X                      185           8140&lt;br /&gt;T                              X                      186           8184&lt;br /&gt;T                              X                      187           8228&lt;br /&gt;T                              X                      188           8272&lt;br /&gt;T                              X                      189           8315&lt;br /&gt;T                              X                      190           8358&lt;br /&gt;T                              X                      191           8401&lt;br /&gt;T                              X                      192           8444&lt;br /&gt;T                              X                      193           8487&lt;br /&gt;T                              X                      194           8530&lt;br /&gt;T                              X                      195           8573&lt;br /&gt;T                              X                      196           8616&lt;br /&gt;T                              X                      197           8659&lt;br /&gt;T                              X                      198           8702&lt;br /&gt;T                              X                      199           8745&lt;br /&gt;T                              X                      200           8788&lt;br /&gt;T                              X                      201           8831&lt;br /&gt;T                              X                      202           8874&lt;br /&gt;T                              X                      203           8917&lt;br /&gt;T                              X                      204           8960&lt;br /&gt;T                              X                      205           9003&lt;br /&gt;T                              X                      206           9046&lt;br /&gt;T                              X                      207           9089&lt;br /&gt;T                              X                      208           9132&lt;br /&gt;T                              X                      209           9175&lt;br /&gt;T                              X                      210           9218&lt;br /&gt;T                              X                      211           9261&lt;br /&gt;T                              X                      212           9304&lt;br /&gt;T                              X                      213           9347&lt;br /&gt;T                              X                      214           9390&lt;br /&gt;T                              X                      215           9433&lt;br /&gt;T                              X                      216           9476&lt;br /&gt;T                              X                      217           9519&lt;br /&gt;T                              X                      218           9562&lt;br /&gt;T                              X                      219           9605&lt;br /&gt;T                              X                      220           9648&lt;br /&gt;T                              X                      221           9691&lt;br /&gt;T                              X                      222           9734&lt;br /&gt;T                              X                      223           9777&lt;br /&gt;T                              X                      224           9820&lt;br /&gt;T                              X                      225           9863&lt;br /&gt;T                              X                      226           9906&lt;br /&gt;T                              X                      227           9949&lt;br /&gt;T                              X                      228           9992&lt;br /&gt;T                              X                      229          10035&lt;br /&gt;T                              X                      230          10078&lt;br /&gt;T                              X                      231          10121&lt;br /&gt;T                              X                      232          10164&lt;br /&gt;T                              X                      233          10207&lt;br /&gt;T                              X                      234          10250&lt;br /&gt;T                              X                      235          10293&lt;br /&gt;T                              X                      236          10336&lt;br /&gt;T                              X                      237          10379&lt;br /&gt;T                              X                      238          10422&lt;br /&gt;T                              X                      239          10465&lt;br /&gt;T                              X                      240          10508&lt;br /&gt;T                              X                      241          10551&lt;br /&gt;T                              X                      242          10594&lt;br /&gt;T                              X                      243          10637&lt;br /&gt;T                              X                      244          10680&lt;br /&gt;T                              X                      245          10723&lt;br /&gt;T                              X                      246          10766&lt;br /&gt;T                              X                      247          10809&lt;br /&gt;T                              X                      248          10852&lt;br /&gt;T                              X                      249          10895&lt;br /&gt;T                              X                      250          10938&lt;br /&gt;T                              X                      251          10981&lt;br /&gt;T                              X                      252          11024&lt;br /&gt;T                              X                      253          11067&lt;br /&gt;T                              X                      254          11110&lt;br /&gt;&lt;br /&gt;75 filas seleccionadas.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;La tabla de arriba se lee diferente a la que info que guardaba el histograma del tipo "FRECUENCY" ya que hay muchos valores para representar Oracle uso el tipo de histograma "HEIGHT BALANCED". Analizando el histograma vemos que la columna ENDPOINT_VALUE tiene 254 valores (buckets). La primera fila:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;TABLE_NAME                     COLUMN_NAM ENDPOINT_NUMBER ENDPOINT_VALUE ENDPOINT&lt;br /&gt;------------------------------ ---------- --------------- -------------- --------&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;T                              X                      113              1&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Tiene 113 buckets asignados. De donde sale es valor?. Pensemeos que tenemos 11110 filas, de las cuales tenemos 5000 con valor 1. Hagamos un poco de aritmetica:&lt;br /&gt;&lt;br /&gt;Si tenemos 11100 filas representadas con 254 buckets y para el valor 1 se asignaron 113 tenemos:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA10G&gt; select (11110/254)*113 from dual;&lt;br /&gt;&lt;br /&gt;(11110/254)*113&lt;br /&gt;---------------&lt;br /&gt;      4942.6378&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Cercano a 5000, no?, veamos que pasa para el valor 2:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;TABLE_NAME                     COLUMN_NAM ENDPOINT_NUMBER ENDPOINT_VALUE ENDPOINT&lt;br /&gt;------------------------------ ---------- --------------- -------------- --------&lt;br /&gt;T                              X                      113              1&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;T                              X                      181              2&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Para sacar la cantidad de buckets del ENDPOINT_VALUE tenemos que hacer:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA10G&gt; select (11110/254)*(181-113) from dual;&lt;br /&gt;&lt;br /&gt;(11110/254)*(181-113)&lt;br /&gt;---------------------&lt;br /&gt;           2974.33071&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Como se ve esta muy cercano a 3000 que es la cantidad real del filas del tipo 2.&lt;br /&gt;La diferencia se da por lo siguiente: Ya que tenemos 11110 filas representadas por 254 barras a buckets entonces tenemos: 11110/254= 43.74 filas por bucket. Los dos valores obtenidos arriba para X=1 y X=2 no son exactos porque se calcula discretizando por bucket. &lt;br /&gt;&lt;br /&gt;Una vez obtenido el histograma veamos el plan que se obtiene para la consulta:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA10G&gt;  explain plan for select count(1) from t where x = 1;&lt;br /&gt;&lt;br /&gt;Explicado.&lt;br /&gt;&lt;br /&gt;rop@DESA10G&gt; select * from table(dbms_xplan.display);&lt;br /&gt;&lt;br /&gt;PLAN_TABLE_OUTPUT&lt;br /&gt;----------------------------------------------------------------------------------------------------&lt;br /&gt;Plan hash value: 1842905362&lt;br /&gt;&lt;br /&gt;---------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |&lt;br /&gt;---------------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT   |      |     1 |     3 |     6   (0)| 00:00:01 |&lt;br /&gt;|   1 |  SORT AGGREGATE    |      |     1 |     3 |            |          |&lt;br /&gt;|*  2 |   &lt;span style="font-weight:bold;"&gt;TABLE ACCESS FULL&lt;/span&gt;| T    |  4943 | 14829 |     6   (0)| 00:00:01 |&lt;br /&gt;---------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;   2 - filter("X"=1)&lt;br /&gt;&lt;br /&gt;14 filas seleccionadas.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Esta vez el optimizador eligió ir por un FULL_SCAN que es el mejor plan ya que tiene que recorrer casi la mitad de la tabla.&lt;br /&gt;&lt;br /&gt;Si buscamos un valor con una sola ocurrencia:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA10G&gt; explain plan for select count(1) from t where x = 999999;&lt;br /&gt;&lt;br /&gt;Explicado.&lt;br /&gt;&lt;br /&gt;rop@DESA10G&gt;  select * from table(dbms_xplan.display);&lt;br /&gt;&lt;br /&gt;PLAN_TABLE_OUTPUT&lt;br /&gt;----------------------------------------------------------------------------------------------------&lt;br /&gt;Plan hash value: 3482591947&lt;br /&gt;&lt;br /&gt;---------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation         | Name  | Rows  | Bytes | Cost (%CPU)| Time     |&lt;br /&gt;---------------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT  |       |     1 |     3 |     1   (0)| 00:00:01 |&lt;br /&gt;|   1 |  SORT AGGREGATE   |       |     1 |     3 |            |          |&lt;br /&gt;|*  2 |   &lt;span style="font-weight:bold;"&gt;INDEX RANGE SCAN&lt;/span&gt;| T_IDX |     1 |     3 |     1   (0)| 00:00:01 |&lt;br /&gt;---------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;   2 - access("X"=999999)&lt;br /&gt;&lt;br /&gt;14 filas seleccionadas.&lt;br /&gt;&lt;br /&gt;rop@DESA10G&gt; &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Se observa que se usa el indice en el plan, que obviamente es el mejor acceso posible.&lt;br /&gt;Por último, es importante prestar atención a la cardinalidad (columna ROWS del plan) para el caso de X=1 el calculo fue: 4943 que es el valor redondeado que obtuvimos mas arriba haciendo: (11110/254)*113. De esto podemos confirmar que el Optimizador consultó el histograma para obtener la cardinalidad mas cercana a la real y por lo tanto generó el plan mas adecuado.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5727738329548735676-3803977271290465772?l=oramdq.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://oramdq.blogspot.com/feeds/3803977271290465772/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://oramdq.blogspot.com/2009/12/una-introduccion-los-histogramas-parte.html#comment-form' title='2 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/3803977271290465772'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/3803977271290465772'/><link rel='alternate' type='text/html' href='http://oramdq.blogspot.com/2009/12/una-introduccion-los-histogramas-parte.html' title='Una introducción a los histogramas. Para que se usan y como se interpretan'/><author><name>Pablo A. Rovedo</name><uri>http://www.blogger.com/profile/02924687287216878757</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://3.bp.blogspot.com/_scsqOe7UOdc/SYDOV9ANLfI/AAAAAAAAAFQ/71M2XO1AZ1M/S220/DSC02067+(Small).JPG'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5727738329548735676.post-8423032337162047884</id><published>2009-11-29T07:02:00.000-08:00</published><updated>2009-11-30T10:06:56.588-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Performance'/><title type='text'>Testeando el commit asincrónico (COMMIT_WRITE)</title><content type='html'>En 10g R2 se introdujo el parámetro COMMIT_WRITE cuya finalidad es permitir el uso de commits asincronicos. Como es sabido cada vez que efectuamos un commit (o rollback) se persiste la informacion de la transacción en los redo logs en forma sincronica, esto significa que hasta que no se haga efectiva dicha escritura en disco, no se retorna el control a la aplicación o proceso que confirmó la transacción. Ahora bien, si pensamos en un commit asincronico estariamos violando la premisa de Durabilidad de una transacción, recordemos que en Oracle las transacciones son compatibles con el concepto ACID (Atomicity, Consistency, Isolation, Durability), y por lo tanto se podrian perder transacciones si se produjera un error severo de Hardware. Al no asegurar la durabilidad de las transacciones no podemos confiar que la base de datos maneje en forma totalmente confiable nuestras transaccciones lo cual resulta en sistemas inestables y propensos a errores e insonsistencias. Despues de todo lo que les comenté se preguntaran para que puede servir este nuevo parámetro, no?, bueno... yo diría que en ciertos casos muy particulares y acotados, donde se pueda tolerar perdida de información y donde se requiera muy alta transaccionabilidad, encontrariamos un beneficio en performance (por ejemplo se reducirian las esperas en el evento log file sync) ya que la ejecución de las sentencias COMMIT es instantanea lo cual genera mayor cantidad de ejecuciones. &lt;br /&gt;&lt;br /&gt;Antes de ir a la prueba me gustaría aclarar que si se ven tentados a usar este parámetro para acelerar el procesamiento, primero chequeen el diseño de transaccionabilidad de su aplicación y vean si es posible una vuelta de tuerca desde el lado del código, ya sea viendo si es posible "commitear" menos frecuentemente o si se esta "commiteando" en forma redundante, antes de ir una solución (quick and dirty) como es tocar el parámetro COMMIT_WRITE.&lt;br /&gt;&lt;br /&gt;Para probar este parámetro, armé un bloque PL/SQL anónimo donde voy a probar las cuatro conbinaciones posibles del COMMIT_WRITE y voy a almacenar los resultados del test en la tabla RES para luego realizar una consulta que me permita comparar en forma sencilla cada opción.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SQL&gt; create table T (X NUMBER(38),&lt;br /&gt;2                    Y VARCHAR2(10),&lt;br /&gt;3                    Z DATE)&lt;br /&gt;/&lt;br /&gt;Table created.&lt;br /&gt;&lt;br /&gt;SQL&gt;&lt;br /&gt;&lt;br /&gt;SQL&gt; create table RES (NAME     VARCHAR2(200) not null,&lt;br /&gt;2                      VALUE    NUMBER,&lt;br /&gt;3                      NCASE    NUMBER(1) not null)&lt;br /&gt;/&lt;br /&gt;Table created.&lt;br /&gt;&lt;br /&gt;rop@BPP3&gt; begin&lt;br /&gt;2   execute immediate 'truncate table res';&lt;br /&gt;3&lt;br /&gt;4   for i in 1..4 loop&lt;br /&gt;5&lt;br /&gt;6    case i when 1 then&lt;br /&gt;7        execute immediate 'alter session set commit_write=''immediate,wait''';&lt;br /&gt;8            when 2 then&lt;br /&gt;9        execute immediate 'alter session set commit_write=''immediate,nowait''';&lt;br /&gt;10           when 3 then&lt;br /&gt;11        execute immediate 'alter session set commit_write=''batch,wait''';&lt;br /&gt;12             when 4 then&lt;br /&gt;13          execute immediate 'alter session set commit_write=''batch,nowait''';&lt;br /&gt;14   end case;&lt;br /&gt;15&lt;br /&gt;16   insert into res&lt;br /&gt;17   select 'STAT...' || a.name name,&lt;br /&gt;18          b.value,&lt;br /&gt;19          case i when 1 then 1&lt;br /&gt;20                 when 2 then 2&lt;br /&gt;21                 when 3 then 3&lt;br /&gt;22                 when 4 then 4&lt;br /&gt;23          end&lt;br /&gt;24   from v$statname a, v$mystat b&lt;br /&gt;25   where a.statistic# = b.statistic#&lt;br /&gt;26   union all&lt;br /&gt;27   select 'LATCH.' || name,&lt;br /&gt;28          gets,&lt;br /&gt;29          case i when 1 then 1&lt;br /&gt;30                 when 2 then 2&lt;br /&gt;31                 when 3 then 3&lt;br /&gt;32                 when 4 then 4&lt;br /&gt;33          end&lt;br /&gt;34   from v$latch&lt;br /&gt;35   union all&lt;br /&gt;36   select 'STAT...Elapsed Time',&lt;br /&gt;37          hsecs,&lt;br /&gt;38          case i when 1 then 1&lt;br /&gt;39                 when 2 then 2&lt;br /&gt;40                 when 3 then 3&lt;br /&gt;41                 when 4 then 4&lt;br /&gt;42          end&lt;br /&gt;43   from v$timer;&lt;br /&gt;44&lt;br /&gt;45   for j in (select trunc(dbms_random.value(1,10000)) x,&lt;br /&gt;46                    dbms_random.string('a',10) y,&lt;br /&gt;47                    sysdate+dbms_random.value(-300,300) z&lt;br /&gt;48             from dual&lt;br /&gt;49              connect by rownum &lt;= 100000)  &lt;br /&gt;50   loop  &lt;br /&gt;51      insert into t values (j.x,j.y,j.z);  &lt;br /&gt;52      commit; &lt;br /&gt;53   end loop; &lt;br /&gt;54&lt;br /&gt;55   for k in (select 'STAT...' || a.name name, b.value  &lt;br /&gt;56             from v$statname a, &lt;br /&gt;57                  v$mystat b  &lt;br /&gt;58             where a.statistic# = b.statistic#  &lt;br /&gt;59             union all  &lt;br /&gt;60             select 'LATCH.' || name,  gets &lt;br /&gt;61             from v$latch  &lt;br /&gt;62             union all  &lt;br /&gt;63             select 'STAT...Elapsed Time',hsecs  &lt;br /&gt;64             from v$timer)  &lt;br /&gt;65   loop&lt;br /&gt;66       update res &lt;br /&gt;67       set value = (k.value-value)&lt;br /&gt;68       where res.name = k.name &lt;br /&gt;69         and res.ncase = i;  &lt;br /&gt;70    end loop; &lt;br /&gt;71    commit; &lt;br /&gt;72  end loop;  &lt;br /&gt;73 end; &lt;br /&gt;/  &lt;br /&gt;PL/SQL procedure successfully completed.    &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Ahora voy a ejecutar una consulta sobre la tabla de resultados de forma tal de encolumnar cada caso y poder comparar mas facilmente.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@BPP3&gt; select name,&lt;br /&gt;            max(decode(ncase,1,value,null)) "IMMEDIATE,WAIT",&lt;br /&gt;            max(decode(ncase,2,value,null)) "IMMEDIATE,NOWAIT",&lt;br /&gt;            max(decode(ncase,3,value,null)) "BATCH,WAIT",&lt;br /&gt;            max(decode(ncase,4,value,null)) "BATCH,NOWAIT"&lt;br /&gt;     from res&lt;br /&gt;     group by name&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;NAME                                                         IMMEDIATE,WAIT IMMEDIATE,NOWAIT BATCH,WAIT BATCH,NOWAIT&lt;br /&gt;&lt;br /&gt;------------------------------------------------------------ -------------- ---------------- ---------- ------------&lt;br /&gt;&lt;br /&gt;STAT...recursive cpu usage                                             1393              887       1293          849&lt;br /&gt;&lt;br /&gt;STAT...messages sent                                                 100040            10449     100008           53&lt;br /&gt;&lt;br /&gt;STAT...commit wait requested                                         100000                0     100000            0&lt;br /&gt;&lt;br /&gt;STAT...SMON posted for undo segment shrink                                0                0          0            0&lt;br /&gt;&lt;br /&gt;STAT...commit batch performed                                             0                0     100000       100000&lt;br /&gt;&lt;br /&gt;STAT...commit immediate performed                                    100000           100000          0            0&lt;br /&gt;&lt;br /&gt;STAT...commit nowait performed                                            0           100000          0       100000&lt;br /&gt;&lt;br /&gt;LATCH.session timer                                                      19                3         34            3&lt;br /&gt;&lt;br /&gt;STAT...redo synch time                                                 4042                0       8777            0&lt;br /&gt;&lt;br /&gt;STAT...commit batch requested                                             0                0     100000       100000&lt;br /&gt;&lt;br /&gt;STAT...commit wait/nowait requested                                  100000           100000     100000       100000&lt;br /&gt;&lt;br /&gt;STAT...commit wait performed                                         100000                0     100000            0&lt;br /&gt;&lt;br /&gt;LATCH.messages                                                       287070            23614     462948          447&lt;br /&gt;&lt;br /&gt;&lt;strong style="font-weight: normal;"&gt;STAT...Elapsed Time                                                    5543              965      10056          927&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;STAT...redo synch writes                                             100000                0     100000            0&lt;br /&gt;&lt;br /&gt;STAT...commit batch/immediate requested                              100000           100000     100000       100000&lt;br /&gt;&lt;br /&gt;STAT...commit immediate requested                                    100000           100000          0            0&lt;br /&gt;&lt;br /&gt;LATCH.Consistent RBA                                                 100035            10447     100015           54&lt;br /&gt;&lt;br /&gt;LATCH.cache buffers lru chain                                           652              600       4870         3282&lt;br /&gt;&lt;br /&gt;LATCH.object queue header heap                                           93               16     216728          344&lt;br /&gt;&lt;br /&gt;LATCH.redo allocation                                                587541           414896     504629       304247&lt;br /&gt;&lt;br /&gt;LATCH.JS queue state obj latch                                          396               72        720           72&lt;br /&gt;&lt;br /&gt;STAT...CPU used by this session                                        1493              956       1388          915&lt;br /&gt;&lt;br /&gt;STAT...session pga memory max                                       4587520            65536          0            0&lt;br /&gt;&lt;br /&gt;STAT...commit nowait requested                                            0           100000          0       100000&lt;br /&gt;&lt;br /&gt;LATCH.client/application info                                             4                0          8            0&lt;br /&gt;&lt;br /&gt;LATCH.OS process allocation                                              19                3         34            3&lt;br /&gt;&lt;br /&gt;LATCH.active checkpoint queue latch                                      24                5      54186           89&lt;br /&gt;&lt;br /&gt;LATCH.archive process latch                                              26                9         46            9&lt;br /&gt;&lt;br /&gt;LATCH.post/wait queue                                                150161                5     150132            3&lt;br /&gt;&lt;br /&gt;LATCH.parameter table allocation management                               2                0          4            0&lt;br /&gt;&lt;br /&gt;LATCH.redo writing                                                   300168            31357     354299          258&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;El resultado de la consulta me arrojó mas de 700 estadisticas y latches. Solo copié&lt;br /&gt;las filas donde noté diferencias importantes en los casos. En especial, la fila resaltada (Elapsed Time) muestra el tiempo total de procesamiento en cada caso en centisegundos. Como se observa para los casos donde se usa WAIT los tiempos son bastante mayores, lo cual comprueba la mejora en performance al usar asincronismo (NOWAIT).&lt;br /&gt;&lt;br /&gt;Mientras escribia esta nota descubrí que en 11g el parametro commit_write esta "deprecated", parece que tuvo poca vida, no?. De todas formas la documentación aclara que fue reemplazado por los parametros COMMIT_WAIT y COMMIT_LOGGING.&lt;br /&gt;Usando estos dos nuevos parametros en conjunto se obtendría el mismo funcionamiento que COMMIT_WRITE, aunque que el parametro COMMIT_WAIT agregó una nueva opción para forzar el wait (force_wait)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5727738329548735676-8423032337162047884?l=oramdq.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://oramdq.blogspot.com/feeds/8423032337162047884/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://oramdq.blogspot.com/2009/11/testeando-el-commit-asincronico.html#comment-form' title='2 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/8423032337162047884'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/8423032337162047884'/><link rel='alternate' type='text/html' href='http://oramdq.blogspot.com/2009/11/testeando-el-commit-asincronico.html' title='Testeando el commit asincrónico (COMMIT_WRITE)'/><author><name>Pablo A. Rovedo</name><uri>http://www.blogger.com/profile/02924687287216878757</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://3.bp.blogspot.com/_scsqOe7UOdc/SYDOV9ANLfI/AAAAAAAAAFQ/71M2XO1AZ1M/S220/DSC02067+(Small).JPG'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5727738329548735676.post-3878881407224082513</id><published>2009-10-22T05:56:00.000-07:00</published><updated>2009-10-22T06:45:28.959-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Seguridad'/><title type='text'>Variante para evitar la creación de sinonimos locales en usuarios de aplicación</title><content type='html'>Es una práctica recomendada y en general impuesta por el area de seguridad de las empresas, crear un esquema de datos (owner) y un esquema de acceso (usuario de explotación o usuario de aplicación) cuando se realiza el setup o deploy de una nueva aplicación o modulo en un base de datos.  Este esquema  imposibilita a los usuarios (excepto obviamente al los usuarios administradores o dba) realizar cambios sobre los objetos del esquema propietario. Las pautas mas importantes a seguir en dicho esquema de layout son:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Los permisos sobre los objetos del owner se deben otorgar por medio de roles hacia los usuarios de explotación (nunca en forma directa). &lt;/li&gt;&lt;li&gt;Los usuarios de explotación solo deben tener otorgado el rol de conexión y el rol de definido para la aplicación. No tendrán quota en ningun tablespace ni podrán crear ningun objeto.&lt;/li&gt;&lt;li&gt;Los objetos otorgados desde el owner hacia el usuario de explotacion deberan ser referenciados mediante un sinonimo local en el usuario de explotación (no referenciar ningun objeto anteponiendo el esquema owner).&lt;/li&gt;&lt;li&gt;Los esquema owner y de explotación y los roles deberán ser creados por el area de base de datos. En el deploy de las app se deberá otorgar los grants al rol y crear los sinonimos locales.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;Una variante interesante para evitar la gestión de sinonimos locales es alterar la sesion (podría ser mediante un on-logon trigger) de forma tal de cambiar el esquema corriente (de explotación a owner) y asi no tener la necesidad de calificar el objeto anteponiendo el esquema.  Les voy a mostrar como implementar esta variante:&lt;br /&gt;&lt;br /&gt;Lo primero es crear un on logon trigger. En el ejemplo mi usuario de explotación de llama exp_rop&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;create or replace trigger SYSTEM.onLogon_trg&lt;br /&gt;after logon on database&lt;br /&gt;Begin                         &lt;br /&gt;  if ( user in ('EXP_ROP')) then&lt;br /&gt;      execute immediate 'alter session set current_schema=ROP';&lt;br /&gt;  end if;&lt;br /&gt;end;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Creo un esquema de explotación y el rol de aplicación (rol_rop). El objeto usado para la prueba&lt;br /&gt;es la tabla ROP.T.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA10G&gt; create user exp_rop identified by exp_rop;&lt;br /&gt;&lt;br /&gt;Usuario creado.&lt;br /&gt;&lt;br /&gt;rop@DESA10G&gt; grant create session to exp_rop;&lt;br /&gt;&lt;br /&gt;Concesión terminada correctamente.&lt;br /&gt;&lt;br /&gt;rop@DESA10G&gt; desc t&lt;br /&gt;Nombre                                                ¿Nulo?   Tipo&lt;br /&gt;----------------------------------------------------- -------- ------------------------------------&lt;br /&gt;C1                                                             VARCHAR2(40)&lt;br /&gt;N1                                                             NUMBER&lt;br /&gt;N2                                                             NUMBER&lt;br /&gt;&lt;br /&gt;rop@DESA10G&gt; create role rol_rop;&lt;br /&gt;&lt;br /&gt;Rol creado.&lt;br /&gt;&lt;br /&gt;rop@DESA10G&gt; grant select on t to rol_rop;&lt;br /&gt;&lt;br /&gt;Concesión terminada correctamente.&lt;br /&gt;&lt;br /&gt;rop@DESA10G&gt; grant rol_rop to exp_rop;&lt;br /&gt;&lt;br /&gt;Concesión terminada correctamente.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Ahora pruebo de conectarme con el usuario de explotación para ver si se ejecuta el trigger y se altera la sesión:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA10G&gt; conn exp_rop/exp_rop@movi10d&lt;br /&gt;Conectado.&lt;br /&gt;exp_rop@DESA10G&gt; desc t&lt;br /&gt;Nombre                                                ¿Nulo?   Tipo&lt;br /&gt;----------------------------------------------------- -------- ------------------------------------&lt;br /&gt;C1                                                             VARCHAR2(40)&lt;br /&gt;N1                                                             NUMBER&lt;br /&gt;N2                                                             NUMBER&lt;br /&gt;&lt;br /&gt;exp_rop@DESA10G&gt; select count(1) from t;&lt;br /&gt;&lt;br /&gt;COUNT(1)&lt;br /&gt;----------&lt;br /&gt;    5000&lt;br /&gt;&lt;br /&gt;exp_rop@DESA10G&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Como se vió pude acceder la tabla T que esta en el esquema ROP sin la necesidad de utilizar un sinonimo local.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5727738329548735676-3878881407224082513?l=oramdq.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://oramdq.blogspot.com/feeds/3878881407224082513/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://oramdq.blogspot.com/2009/10/variante-para-evitar-la-creacion-de.html#comment-form' title='0 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/3878881407224082513'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/3878881407224082513'/><link rel='alternate' type='text/html' href='http://oramdq.blogspot.com/2009/10/variante-para-evitar-la-creacion-de.html' title='Variante para evitar la creación de sinonimos locales en usuarios de aplicación'/><author><name>Pablo A. Rovedo</name><uri>http://www.blogger.com/profile/02924687287216878757</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://3.bp.blogspot.com/_scsqOe7UOdc/SYDOV9ANLfI/AAAAAAAAAFQ/71M2XO1AZ1M/S220/DSC02067+(Small).JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5727738329548735676.post-3891256274504517515</id><published>2009-09-24T10:59:00.000-07:00</published><updated>2009-09-24T11:30:57.193-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Scripts'/><title type='text'>Reporte de Tablas sin estadisticas o con estadisticas viejas (stale) para una sesion dada</title><content type='html'>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.&lt;br /&gt;El script de abajo permite determinar automaticamente que tablas, particiones y subparticiones tienen estadisticas desactualizadas para un sid determinado.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;set line 120&lt;br /&gt;set pagesize 999&lt;br /&gt;set verify off&lt;br /&gt;&lt;br /&gt;col owner format a15&lt;br /&gt;col table_name format a30&lt;br /&gt;col partition_name format a30&lt;br /&gt;col subpartition_name format a30&lt;br /&gt;&lt;br /&gt;PROMPT&lt;br /&gt;PROMPT "---------------------------------------------"&lt;br /&gt;PROMPT "Reporte de Tablas con estadisticas STALE     " &lt;br /&gt;PROMPT "o nulas para una sesion dada                 "&lt;br /&gt;PROMPT "---------------------------------------------"&lt;br /&gt;ACCEPT sid PROMPT "Ingrese SID a evaluar: "&lt;br /&gt;&lt;br /&gt;select st.owner owner,&lt;br /&gt;    st.table_name table_name,&lt;br /&gt;    st.partition_name partition_name,&lt;br /&gt;    st.subpartition_name subpartition_name&lt;br /&gt;from v$session s,&lt;br /&gt;  v$sql_plan p,&lt;br /&gt;  dba_tab_statistics st&lt;br /&gt;where s.sql_id = p.sql_id&lt;br /&gt;and p.object_owner = st.owner&lt;br /&gt;and p.object_name = st.table_name&lt;br /&gt;and s.sid = &amp;amp;sid&lt;br /&gt;and nvl(st.stale_stats,'YES') = 'YES'&lt;br /&gt;and ((nvl(st.partition_position,1)&lt;br /&gt;  between&lt;br /&gt; (case when (REGEXP_LIKE(nvl(p.partition_start,'a'),'[^[:digit:]]'))&lt;br /&gt;  then 1&lt;br /&gt;  else to_number(p.partition_start) end)&lt;br /&gt;  and  (case when&lt;br /&gt;(REGEXP_LIKE(nvl(p.partition_stop,'a'),'[^[:digit:]]'))&lt;br /&gt;       then 10000&lt;br /&gt;       else to_number(p.partition_stop) end))&lt;br /&gt;    or&lt;br /&gt;    (nvl(st.subpartition_position,1) between&lt;br /&gt;  (case when (REGEXP_LIKE(nvl(p.partition_start,'a'),'[^[:digit:]]'))&lt;br /&gt;       then 1&lt;br /&gt;       else to_number(p.partition_start) end)&lt;br /&gt;       and  (case when&lt;br /&gt;(REGEXP_LIKE(nvl(p.partition_stop,'a'),'[^[:digit:]]')) then 10000&lt;br /&gt;       else to_number(p.partition_stop) end))&lt;br /&gt;   )&lt;br /&gt;/&lt;br /&gt;&lt;br /&gt;set verify on  &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;IMPORTANTE: Para asegurar que esten impactados los cambios mas recientes en la&lt;br /&gt;vista dba_tab_statistics es recomendable flushear la memoria de la&lt;br /&gt;siguiente forma: dbms_stats.flush_database_monitoring_info. &lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5727738329548735676-3891256274504517515?l=oramdq.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://oramdq.blogspot.com/feeds/3891256274504517515/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://oramdq.blogspot.com/2009/09/reporte-de-tablas-sin-estadisticas-o.html#comment-form' title='0 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/3891256274504517515'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/3891256274504517515'/><link rel='alternate' type='text/html' href='http://oramdq.blogspot.com/2009/09/reporte-de-tablas-sin-estadisticas-o.html' title='Reporte de Tablas sin estadisticas o con estadisticas viejas (stale) para una sesion dada'/><author><name>Pablo A. Rovedo</name><uri>http://www.blogger.com/profile/02924687287216878757</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://3.bp.blogspot.com/_scsqOe7UOdc/SYDOV9ANLfI/AAAAAAAAAFQ/71M2XO1AZ1M/S220/DSC02067+(Small).JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5727738329548735676.post-1087402211542196573</id><published>2009-09-23T08:47:00.001-07:00</published><updated>2010-04-01T13:08:34.150-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Scripts'/><title type='text'>Reporte historico de tiempo de ejecucion máxima, mínima y promedio de sentencias SQL</title><content type='html'>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. &lt;br /&gt;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). &lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;select sql_id,&lt;br /&gt;       to_char(trunc(max(max_ela_time)/60/60),'09')||&lt;br /&gt;       to_char(trunc(mod(max(max_ela_time),3600)/60),'09')||&lt;br /&gt;       to_char(mod(mod(max(max_ela_time),3600),60),'09') max_ela_time,&lt;br /&gt;       to_char(max(max_ela_time_dt),'DD/MM/YYYY HH24:MI') max_ela_time_dt, &lt;br /&gt;       to_char(trunc(min(min_ela_time)/60/60),'09')||&lt;br /&gt;       to_char(trunc(mod(min(min_ela_time),3600)/60),'09')||&lt;br /&gt;       to_char(mod(mod(min(min_ela_time),3600),60),'09') min_ela_time,&lt;br /&gt;       to_char(min(min_ela_time_dt),'DD/MM/YYYY HH24:MI') min_ela_time_dt, &lt;br /&gt;       to_char(trunc(avg(avg_ela_time)/60/60),'09')||&lt;br /&gt;       to_char(trunc(mod(avg(avg_ela_time),3600)/60),'09')||&lt;br /&gt;       to_char(mod(mod(avg(avg_ela_time),3600),60),'09') avg_ela_time&lt;br /&gt;from&lt;br /&gt;(select unique sql_id,&lt;br /&gt;       round((first_value(elapsed_time) &lt;br /&gt;        over (partition by sql_id order by elapsed_time desc))/executions/1000000) max_ela_time,&lt;br /&gt;       first_value(dt) over (partition by sql_id order by elapsed_time desc) max_ela_time_dt,&lt;br /&gt;       round((first_value(elapsed_time) &lt;br /&gt;        over (partition by sql_id order by elapsed_time))/executions/1000000) min_ela_time,&lt;br /&gt;       first_value(dt) &lt;br /&gt;        over (partition by sql_id order by elapsed_time) min_ela_time_dt,&lt;br /&gt;       round((avg(elapsed_time) over (partition by sql_id))/executions/1000000) avg_ela_time&lt;br /&gt;from (select unique &lt;br /&gt;          ss.sql_id,&lt;br /&gt;          s.snap_id,&lt;br /&gt;          lag (s.snap_id) over (partition by s.startup_time,ss.sql_id order by ss.snap_id desc) snap_id_n,&lt;br /&gt;          ss.elapsed_time_total elapsed_time,&lt;br /&gt;          s.begin_interval_time dt,&lt;br /&gt;          lag (ss.elapsed_time_total) &lt;br /&gt;            over (partition by s.startup_time,ss.sql_id order by s.snap_id desc ) elapsed_time_n,&lt;br /&gt;          lag (s.begin_interval_time) &lt;br /&gt;            over (partition by s.startup_time,ss.sql_id order by s.snap_id desc ) dt_n,&lt;br /&gt;          executions_total executions,&lt;br /&gt;          lag (ss.executions_total) &lt;br /&gt;            over (partition by s.startup_time,ss.sql_id order by s.snap_id desc ) executions_n&lt;br /&gt;      from dba_hist_sqlstat ss,&lt;br /&gt;           dba_hist_snapshot s&lt;br /&gt;      where s.snap_id = ss.snap_id)&lt;br /&gt;where elapsed_time &gt; elapsed_time_n&lt;br /&gt;  and executions != 0)&lt;br /&gt;group by sql_id&lt;br /&gt;order by avg_ela_time desc&lt;br /&gt;/&lt;br /&gt;&lt;br /&gt;SQL_ID        MAX_ELA_T MAX_ELA_TIME_DT  MIN_ELA_T MIN_ELA_TIME_DT  AVG_ELA_T&lt;br /&gt;------------- --------- ---------------- --------- ---------------- ---------&lt;br /&gt;89qyn4bbt03jq  00 05 24 22/09/2009 18:00  00 00 56 22/09/2009 06:00  00 03 06&lt;br /&gt;gfjvxb25b773h  00 00 13 22/09/2009 17:48  00 00 13 22/09/2009 17:48  00 00 13&lt;br /&gt;a1axyycsv1fb1  00 00 06 21/09/2009 18:00  00 00 06 21/09/2009 18:00  00 00 06&lt;br /&gt;fqmpmkfr6pqyk  00 00 05 21/09/2009 12:00  00 00 05 21/09/2009 12:00  00 00 05&lt;br /&gt;b7jn4mf49n569  00 00 05 21/09/2009 20:00  00 00 05 21/09/2009 20:00  00 00 05&lt;br /&gt;4c1xvq9ufwcjc  00 00 03 22/09/2009 17:48  00 00 03 22/09/2009 17:48  00 00 03&lt;br /&gt;06fhnfwzpzvug  00 00 03 22/09/2009 17:48  00 00 03 22/09/2009 17:48  00 00 03&lt;br /&gt;ahtrk133zdqa5  00 00 02 21/09/2009 22:00  00 00 02 21/09/2009 22:00  00 00 02&lt;br /&gt;bunssq950snhf  00 00 02 21/09/2009 19:00  00 00 02 21/09/2009 19:00  00 00 02&lt;br /&gt;d92h3rjp0y217  00 00 01 23/09/2009 03:00  00 00 00 21/09/2009 19:00  00 00 01&lt;br /&gt;abtp0uqvdb1d3  00 00 02 23/09/2009 08:00  00 00 00 22/09/2009 06:00  00 00 01&lt;br /&gt;8bfst16kjukv6  00 00 01 22/09/2009 17:48  00 00 01 22/09/2009 17:48  00 00 01&lt;br /&gt;6jgrbypm756nu  00 00 01 21/09/2009 11:00  00 00 01 21/09/2009 11:00  00 00 01&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5727738329548735676-1087402211542196573?l=oramdq.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://oramdq.blogspot.com/feeds/1087402211542196573/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://oramdq.blogspot.com/2009/09/reporte-de-tiempo-de-ejecucion-maxima.html#comment-form' title='0 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/1087402211542196573'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/1087402211542196573'/><link rel='alternate' type='text/html' href='http://oramdq.blogspot.com/2009/09/reporte-de-tiempo-de-ejecucion-maxima.html' title='Reporte historico de tiempo de ejecucion máxima, mínima y promedio de sentencias SQL'/><author><name>Pablo A. Rovedo</name><uri>http://www.blogger.com/profile/02924687287216878757</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://3.bp.blogspot.com/_scsqOe7UOdc/SYDOV9ANLfI/AAAAAAAAAFQ/71M2XO1AZ1M/S220/DSC02067+(Small).JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5727738329548735676.post-5570989025113323098</id><published>2009-09-16T04:42:00.000-07:00</published><updated>2010-09-09T12:48:10.926-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='CBO'/><category scheme='http://www.blogger.com/atom/ns#' term='Upgrade'/><title type='text'>Como minimizar problemas luego de un upgrade de versión de Oracle  (Caso2:  Cambio del orden de evaluación de predicados)</title><content type='html'>En esta nueva nota les voy a contar un problema con el que se pueden encontrar al migrar desde version 8i hacia 9i o superior. El principal tema radica en la evolución constante que va teniendo el optimizador para estimar el costo de acceso a los datos de las sentencias. En la versión 7, donde apareció por primera vez el optimizador por costos, el costo se calculaba simplemente ponderando por la cantidad de requerimientos de lectura a disco. Esto, como es sabido, provocó un rechazo a cambiar de RBO a CBO bastante generalizado en su momento, dado que los planes de ejecución comenzaban a hacer cosas extrañas, causando importantes problemas de rendimiento generalizado. Por tal motivo, la mayoria de las compañias continuaron usando el optimizador por reglas, ya que les garantizaba que no se alteraran los planes y que no se destabilizaran las aplicaciones. El principal problema de usar solo los read request para generar el costo en Oracle 7 fue no considerar el caching que existe en distintos niveles.&lt;br /&gt;&lt;br /&gt;A partir de 8i, se comenzó a ponderar por tipo de lectura (single reads y multiblock reads) y por tamaño y tiempo de lectura. Esto mejoró bastante la calidad de los planes generados y dió mayor confianza a las empresas para animarse a cambiar a CBO, mas que nada porque Oracle Corporation comenzó a incentivar fuertemente a salir de RBO, discontinuado a partir de 7.x (año 1992) y desoportado desde 10g. Si bien el comportamiento en 8i fue mucho mas estable faltaba tomar en cuenta algo muy importante, el tiempo de cpu.&lt;br /&gt;&lt;br /&gt;Recien a partir de 9i se incluyó en el calculo del costo el tiempo insumido en procesamiento de cpu, anteriormente la formula para calcular solo tomaba en cuenta caracteristicas de i/o. En consecuencia con esta nueva variable de ponderación el optimizador puede cambiar el orden de evaluación de los predicados si estima que con ese nuevo orden se minimiza el tiempo de cpu. Este reordenamiento puede causar que ciertas consultas fallen en 9i+ y no en 8i. Este fallo generalmente se debe a una inconsistencia de datos, que antes quedaba "tapada" y que ahora al evaluar en otro orden genera un error, por ejemplo con una conversión implicita.&lt;br /&gt;&lt;br /&gt;Vayamos a los ejemplos para graficar mejor este tema:&lt;br /&gt;&lt;br /&gt;Primero voy a crear la siguiente tabla:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA10G&gt; create table t as&lt;br /&gt; 2  select to_char(mod(rownum,30)) c1,&lt;br /&gt; 3         rownum n1,&lt;br /&gt; 4         mod(rownum,30) n2&lt;br /&gt; 5  from dual&lt;br /&gt; 6  connect by rownum &lt;= 5000;  Tabla creada. &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;La tabla T tiene 3 columnas:&lt;br /&gt;C1: solo tendrá valores 30 posibles valores (de 0 a 29) y es de tipo varchar2.&lt;br /&gt;N1: tiene valores distintos del 1 al 5000.&lt;br /&gt;N2: tiene los mismos valores de C1 pero es de tipo number.&lt;br /&gt;&lt;br /&gt;Para simular estar en 8i, voy a cambiar el parametro de session "optimizer_features_enable" para que el optimizador se comporte con un optimizador de 8i.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA10G&gt; alter session set optimizer_features_enable = '8.1.7';&lt;br /&gt;&lt;br /&gt;Sesión modificada.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Ahora, ejecuto una sentencia que cuenta la cantidad de filas filtrando la tabla&lt;br /&gt;por los tres campos.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA10G&gt; ed&lt;br /&gt;Escrito file afiedt.buf&lt;br /&gt;&lt;br /&gt; 1  explain plan for&lt;br /&gt; 2  select count(1) from t&lt;br /&gt; 3* where c1 = 1 and n1 = 1111 and n2 = 1&lt;br /&gt;rop@DESA10G&gt; /&lt;br /&gt;&lt;br /&gt;Explicado.&lt;br /&gt;&lt;br /&gt;rop@DESA10G&gt; select * from table(dbms_xplan.display);&lt;br /&gt;&lt;br /&gt;PLAN_TABLE_OUTPUT&lt;br /&gt;----------------------------------------------------------------------------------------------------&lt;br /&gt;Plan hash value: 1842905362&lt;br /&gt;&lt;br /&gt;-----------------------------------------------------------&lt;br /&gt;| Id  | Operation          | Name | Rows  | Bytes | Cost  |&lt;br /&gt;-----------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT   |      |     1 |    48 |     2 |&lt;br /&gt;|   1 |  SORT AGGREGATE    |      |     1 |    48 |       |&lt;br /&gt;|*  2 |   TABLE ACCESS FULL| T    |     1 |    48 |     2 |&lt;br /&gt;-----------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;  &lt;span style="font-weight: bold;"&gt;2 - filter(TO_NUMBER("C1")=1 AND "N1"=1111 AND "N2"=1)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Note&lt;br /&gt;-----&lt;br /&gt;  - cpu costing is off (consider enabling it)&lt;br /&gt;&lt;br /&gt;18 filas seleccionadas.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Mirando la información predicados (Predicate Information) notamos dos cosas interesantes. Primero hay una nota que nos advierte que el costo por cpu esta en off. Lo cual es lógico, por lo que comenté mas arriba respecto a que 8i no ponderaba por cpu y si bien estamos en 10g, recordemos que cambié el comportamiento del optimizador a 8i. Segundo vemos que los predicados se evaluaron en orden y que hubo una conversión implicita (TO_NUMBER()).&lt;br /&gt;&lt;br /&gt;Vuelvo a poner el optimizador a su valor default y me fijo evaluo el plan:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA10G&gt; alter session set optimizer_features_enable = '10.2.0.4';&lt;br /&gt;&lt;br /&gt;Sesión modificada.&lt;br /&gt;&lt;br /&gt;rop@DESA10G&gt; explain plan for&lt;br /&gt; 2  select count(1) from t&lt;br /&gt; 3  where c1 = 1 and n1 = 1111 and n2 = 1;&lt;br /&gt;&lt;br /&gt;Explicado.&lt;br /&gt;&lt;br /&gt;rop@DESA10G&gt; select * from table(dbms_xplan.display);&lt;br /&gt;&lt;br /&gt;PLAN_TABLE_OUTPUT&lt;br /&gt;----------------------------------------------------------------------------------------------------&lt;br /&gt;Plan hash value: 1842905362&lt;br /&gt;&lt;br /&gt;---------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |&lt;br /&gt;---------------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT   |      |     1 |    48 |     5   (0)| 00:00:01 |&lt;br /&gt;|   1 |  SORT AGGREGATE    |      |     1 |    48 |            |          |&lt;br /&gt;|*  2 |   TABLE ACCESS FULL| T    |     1 |    48 |     5   (0)| 00:00:01 |&lt;br /&gt;---------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt; &lt;span style="font-weight: bold;"&gt; 2 - filter("N1"=1111 AND "N2"=1 AND TO_NUMBER("C1")=1)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Note&lt;br /&gt;-----&lt;br /&gt;  - dynamic sampling used for this statement&lt;br /&gt;&lt;br /&gt;18 filas seleccionadas.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;El plan no cambió, se hizo sampleo dinamico ya que no habia recolectado estadisticas, pero lo mas importante a destacar es el cambio en la evaluación de los filtros. Notemos que ahora la conversión implicita se dejó para lo ultimo. Por que hizo eso el optimizador?, bien, pensemos que una conversión implica ciclos de cpu y entonces, porque no mejor evaluarlo al final cuando seguramente queden menos filas, ya que se van filtrando con los dos predicados o filtros anteriores, y asi minimizar la cantidad de conversiones, no?. Como ya dijimos el optimizador en versiones 9i+  se preocupa por el costo de procesamiento de cpu y por lo tanto puede realizar ciertos ajustes (reordenamiento de predicados, merge de subqueries, etc) si con eso se reduce la utilización de cpu.&lt;br /&gt;&lt;br /&gt;Analicemos mas en detalle el ejemplo y como se evalua con cpu costing en ON y en OFF&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Con CPU Costing en OFF (8i)&lt;br /&gt;&lt;br /&gt;filter(TO_NUMBER("C1")=1 AND "N1"=1111 AND "N2"=1)&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;El primer predicado (TO_NUMBER("C1")=1) evalua 5000 filas y como resultado de ese filtro se queda con 167 filas.&lt;br /&gt;El segundo predicado (N1=1111) evalua 167 filas y se queda con 1.&lt;br /&gt;El tercer predicado (N2=1) evalua 1 y devuelve 1 (el resultado del count()). &lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Con CPU Costing en ON (9i+)&lt;br /&gt;&lt;br /&gt;filter("N1"=1111 AND "N2"=1 AND TO_NUMBER("C1")=1)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;El primer predicado (N1=1111) evalua 5000 filas y solo devuelve 1 fila.&lt;br /&gt;El segundo predicado (N2=1) compara la fila y se la pasa al siguiente predicado.&lt;br /&gt;El tercer predicado (TO_NUMBER("C1")=1) evalua una sola fila y devuelve el resultado.&lt;br /&gt;&lt;br /&gt;En base al ejemplo analizado se ve claramente que con cpu costing apagado se deben realizar 5000 operaciones implicitas contra 1 operacion implicita cuando se tiene activado el costeo por cpu. Con este ejemplo sencillo, pero representativo, se puede ver lo importante del ordenamiento de la evaluación para minimizar el uso de recurso de cpu y por ende mejorar el tiempo de respuesta general.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Ahora veamos como fijar el orden de evaluación sin cambiar el comportamiento general de la sesion usando el hint "ordered_predicates":&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA10G&gt; ed&lt;br /&gt;Escrito file afiedt.buf&lt;br /&gt;&lt;br /&gt; 1  explain plan for&lt;br /&gt; 2  select /*+ ordered_predicates */ count(1) from t&lt;br /&gt; 3* where c1 = 1 and n1 = 1111 and n2 = 1&lt;br /&gt;rop@DESA10G&gt; /&lt;br /&gt;&lt;br /&gt;Explicado.&lt;br /&gt;&lt;br /&gt;rop@DESA10G&gt;  select * from table(dbms_xplan.display);&lt;br /&gt;&lt;br /&gt;PLAN_TABLE_OUTPUT&lt;br /&gt;----------------------------------------------------------------------------------------------------&lt;br /&gt;Plan hash value: 1842905362&lt;br /&gt;&lt;br /&gt;---------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |&lt;br /&gt;---------------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT   |      |     1 |    48 |     5   (0)| 00:00:01 |&lt;br /&gt;|   1 |  SORT AGGREGATE    |      |     1 |    48 |            |          |&lt;br /&gt;|*  2 |   TABLE ACCESS FULL| T    |     1 |    48 |     5   (0)| 00:00:01 |&lt;br /&gt;---------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;  2 - filter(TO_NUMBER("C1")=1 AND "N1"=1111 AND "N2"=1)&lt;br /&gt;&lt;br /&gt;Note&lt;br /&gt;-----&lt;br /&gt;  - dynamic sampling used for this statement&lt;br /&gt;&lt;br /&gt;18 filas seleccionadas.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Con el hint la evaluación es ordenada, tal cual hubiese sido en 8i por default.&lt;br /&gt;En el plan no se muestró el costo de cpu, pero consultando la tabla de soporte PLAN_TABLE podemos ver que costos fueron asignados de acuerdo al orden de evaluación.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA10G&gt; ed&lt;br /&gt;Escrito file afiedt.buf&lt;br /&gt;&lt;br /&gt; 1  select filter_predicates,cpu_cost from plan_table&lt;br /&gt; 2* where filter_predicates is not null&lt;br /&gt;rop@DESA10G&gt; /&lt;br /&gt;&lt;br /&gt;FILTER_PREDICATES                                    CPU_COST&lt;br /&gt;-------------------------------------------------- ----------&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;"N1"=1000 AND "N2"=1 AND TO_NUMBER("C1")=1           1302275&lt;br /&gt;TO_NUMBER("C1")=1 AND "N1"=1111 AND "N2"=1           1802225&lt;/span&gt;&lt;br /&gt;rop@DESA10G&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;El costo de cpu al evaluar en 10g fue de 1302275 (con reordenamiento) y el costo de cpu sin ordenamiento fue de 1802225 (8i)&lt;br /&gt;&lt;br /&gt;Como demostré con el ejemplo, el optimizador evalua los predicados de forma tal de optimizar el uso de cpu. Este cambio podría hacer fallar ciertos codigos que antes funcionaban y que tenian inconsistencias de datos, como por ejemplo, tener valores no numericos sobre campos varchar que deben tener numeros, y entonces al convertir implicitamente se genere un error de invalid number (ORA-01722). Queda claro que si ocurre esto es debido a un mal diseño, ya que una columna que aloja solo números no debería ser de tipo varchar, ya que de esta forma se promueve, por un lado inconsistencias en los datos y por otro lado, se afecta el rendimiento debido a las conversiones implicitas.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5727738329548735676-5570989025113323098?l=oramdq.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://oramdq.blogspot.com/feeds/5570989025113323098/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://oramdq.blogspot.com/2009/09/como-minimizar-problemas-luego-de-un.html#comment-form' title='1 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/5570989025113323098'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/5570989025113323098'/><link rel='alternate' type='text/html' href='http://oramdq.blogspot.com/2009/09/como-minimizar-problemas-luego-de-un.html' title='Como minimizar problemas luego de un upgrade de versión de Oracle  (Caso2:  Cambio del orden de evaluación de predicados)'/><author><name>Pablo A. Rovedo</name><uri>http://www.blogger.com/profile/02924687287216878757</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://3.bp.blogspot.com/_scsqOe7UOdc/SYDOV9ANLfI/AAAAAAAAAFQ/71M2XO1AZ1M/S220/DSC02067+(Small).JPG'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5727738329548735676.post-5627592744758338011</id><published>2009-09-10T06:13:00.000-07:00</published><updated>2009-12-04T07:33:51.322-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='CBO'/><category scheme='http://www.blogger.com/atom/ns#' term='Upgrade'/><title type='text'>Como minimizar problemas luego de un upgrade de versión de Oracle  (Caso1:  Ordenamiento implicito de GROUP BY en versiones anteriores a 10g R2)</title><content type='html'>Esta nota pretende ser la primera de una serie de notas en las que voy a comentarles problemas que se pueden dar luego de un upgrade de versión de base de datos. Este año estuve involucrado en unos cuantos upgrades de versiones, siempre hacia version 10g R2 pero partiendo desde distintas versiones base (8i, 9i R2 y 10g R1). Aqui en Argentina todavia son muy pocos los que migraron a 11g ya que es una practica habitual, y en mi opinión muy acertada, esperar al segundo release para upgradear. Al momento de esta nota acaba de salir el 11g R2, pero solo para linux, asi que estimo que en poco tiempo tendremos disponible el nuevo release para las plataformas unix "grandes", tales como solaris, aix, hp-ux y tambien para la familia de SO's de windows.&lt;br /&gt;&lt;br /&gt;Es sabido que cada nueva versión de Oracle introduce nuevos features, y en especial features que cambian el comportamiento del optimizador y que producen cambios en los paths de los planes de ejecución. Los cambios en el optimizador son generalmente para mejorar el acceso a los datos y por ende reducir el tiempo de respuesta. Esta mejora en la "inteligencia" del optimizador no debería ocacionar cambios de comportamiento, a menos que no se cumplan las Buenas Practicas de confección de sentencias sql. Una mala práctica, y por desgracia bastante común, es confiar en el ordenamiento implicito que se da, por ejemplo, al usar distint/unique o en el ordenamiento que se produce con el group by. Este último es a veces innecesario y agrega un path implicito para ordenar que suma un tiempo mas antes de retornar la respuesta.&lt;br /&gt;&lt;br /&gt;A partir de 10g R2 cambió el path SORT GROUP BY por el path HASH GROUP BY mejorando el rendimiento dado que no se infiere la necesidad de retornar el resultado ordenado. Todas las unidades de código que "confiaban" en este ordenamiento implicito y que necesitan por negocio un cierto orden van a comenzar a devolver resultados erroneos al upgradear a 10g R2 o versión superior, recordemos que las "Best Practices" dictan usar siempre ORDER BY cuando debe haber un orden ya que el comportamiento no esta garantizado a futuro.&lt;br /&gt;&lt;br /&gt;Ahora, como suelo hacer, les voy a mostrar el ejemplo del group by, en próximas notas le voy a mostrar otros "issues" que pueden causar fuertes dolores de cabeza cuando no se detectan a tiempo.&lt;br /&gt;&lt;br /&gt;Voy a usar mi conocida, y nunca bien ponderada, tablita de ejemplo T&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;rop@TEST10G&gt; select * from v$version;&lt;br /&gt;&lt;br /&gt;BANNER&lt;br /&gt;----------------------------------------------------------------&lt;br /&gt;Oracle Database 10g Enterprise Edition Release 10.2.0.4.0 - 64bi&lt;br /&gt;PL/SQL Release 10.2.0.4.0 - Production&lt;br /&gt;CORE    10.2.0.4.0      Production&lt;br /&gt;TNS for Solaris: Version 10.2.0.4.0 - Production&lt;br /&gt;NLSRTL Version 10.2.0.4.0 - Production&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;rop@TEST10G&gt; create table t as select * from dba_objects;&lt;br /&gt;&lt;br /&gt;Tabla creada.&lt;br /&gt;&lt;br /&gt;rop@TEST10G&gt; exec dbms_stats.gather_table_Stats(user,'T');&lt;br /&gt;&lt;br /&gt;Procedimiento PL/SQL terminado correctamente.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;rop@TEST10G&gt; select object_type,count(1)&lt;br /&gt;2         from t&lt;br /&gt;3         group by object_type&lt;br /&gt;&lt;br /&gt;OBJECT_TYPE           COUNT(1)&lt;br /&gt;------------------- ----------&lt;br /&gt;INDEX                    22538&lt;br /&gt;JOB CLASS                    2&lt;br /&gt;CONTEXT                      5&lt;br /&gt;TABLE SUBPARTITION          18&lt;br /&gt;TYPE BODY                  174&lt;br /&gt;INDEXTYPE                   10&lt;br /&gt;PROCEDURE                  252&lt;br /&gt;RESOURCE PLAN                4&lt;br /&gt;RULE                         4&lt;br /&gt;JAVA CLASS               16417&lt;br /&gt;SCHEDULE                     1&lt;br /&gt;TABLE PARTITION            625&lt;br /&gt;WINDOW                       2&lt;br /&gt;WINDOW GROUP                 1&lt;br /&gt;JAVA RESOURCE              770&lt;br /&gt;TABLE                    22631&lt;br /&gt;TYPE                      1941&lt;br /&gt;VIEW                      3804&lt;br /&gt;LIBRARY                    150&lt;br /&gt;FUNCTION                   329&lt;br /&gt;TRIGGER                    565&lt;br /&gt;PROGRAM                     12&lt;br /&gt;MATERIALIZED VIEW            3&lt;br /&gt;DATABASE LINK                5&lt;br /&gt;CLUSTER                     10&lt;br /&gt;SYNONYM                  23307&lt;br /&gt;PACKAGE BODY               807&lt;br /&gt;QUEUE                       27&lt;br /&gt;CONSUMER GROUP               6&lt;br /&gt;EVALUATION CONTEXT          14&lt;br /&gt;RULE SET                    19&lt;br /&gt;DIRECTORY                   16&lt;br /&gt;UNDEFINED                    6&lt;br /&gt;OPERATOR                    57&lt;br /&gt;JAVA DATA                  306&lt;br /&gt;DIMENSION                    5&lt;br /&gt;SEQUENCE                  2759&lt;br /&gt;LOB                        713&lt;br /&gt;PACKAGE                    866&lt;br /&gt;JOB                         18&lt;br /&gt;INDEX PARTITION            617&lt;br /&gt;LOB PARTITION                1&lt;br /&gt;XML SCHEMA                  26&lt;br /&gt;&lt;br /&gt;43 filas seleccionadas.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;El listado salio desordenado, veamos el plan que genera:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@TEST10G&gt;  explain plan for&lt;br /&gt;2   select object_type,count(1)&lt;br /&gt;3   from t&lt;br /&gt;4   group by object_type;&lt;br /&gt;&lt;br /&gt;Explicado.&lt;br /&gt;&lt;br /&gt;rop@TEST10G&gt; select * from table(dbms_xplan.display);&lt;br /&gt;&lt;br /&gt;PLAN_TABLE_OUTPUT&lt;br /&gt;----------------------------------------------------------------------------------------------------&lt;br /&gt;Plan hash value: 2963600285&lt;br /&gt;&lt;br /&gt;---------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |&lt;br /&gt;---------------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT   |      |    26 |   208 |   339   (7)| 00:00:05 |&lt;br /&gt;|   1 |  HASH GROUP BY     |      |    26 |   208 |   339   (7)| 00:00:05 |&lt;br /&gt;|   2 |   TABLE ACCESS FULL| T    | 99843 |   780K|   324   (2)| 00:00:04 |&lt;br /&gt;---------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;9 filas seleccionadas.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;El path es HASH GROUP BY en reemplazo de SORT HASH GROUP&lt;br /&gt;&lt;br /&gt;Ahora, voy a usar, el truco mas rápido para salir del paso, tipo de solución "quick and dirty", pero solución al fin, que me ha salvado varias veces cuando se pasó por alto algún nuevo mecanismo y se comienzan a ver los problemas en plena hora pico o cuando cancelan procesos baths al dia siguiente del upgrade. Podemos setear a nivel sesion el parámetro "optimizer_features_enable", tambien se puede setear a nivel de hint con opt_param(param,valor), para hacer un flashback al comportamiento de un versión anterior. Como el caso de esta nota se da a partir de 10g R2, como estrategia siempre tomo la decisión de ir al upgrade anterior mas próximo donde funciona como antes, para asi estabilizar el comportamiento y no tener que retornar al release original. Como dije antes, en este caso la solución definitiva sera disparar un requerimiento de cambio de código y que el sector de desarrollo agregue el order by en las sentencias que necesitan del ordenamiento para funcionar correctamente.&lt;br /&gt;&lt;/valor&gt;&lt;a id="publishButton" class="cssButton" href="javascript:void(0)" target="" onclick="if (this.className.indexOf(&amp;quot;ubtn-disabled&amp;quot;) == -1) {var e = document['stuffform'].publish;(e.length) ? e[0].click() : e.click(); if (window.event) window.event.cancelBubble = true; return false;}"&gt;&lt;div class="cssButtonOuter"&gt;&lt;div class="cssButtonMiddle"&gt;&lt;div class="cssButtonInner"&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;&lt;br /&gt;&lt;valor&gt;&lt;pre&gt;&lt;br /&gt;rop@TEST10G&gt; alter session set optimizer_features_enable = '10.1.0.5';&lt;br /&gt;&lt;br /&gt;Sesión modificada.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;rop@TEST10G&gt;  select object_type,count(1)&lt;br /&gt;2           from t&lt;br /&gt;3           group by object_type;&lt;br /&gt;&lt;br /&gt;OBJECT_TYPE           COUNT(1)&lt;br /&gt;------------------- ----------&lt;br /&gt;CLUSTER                     10&lt;br /&gt;CONSUMER GROUP               6&lt;br /&gt;CONTEXT                      5&lt;br /&gt;DATABASE LINK                5&lt;br /&gt;DIMENSION                    5&lt;br /&gt;DIRECTORY                   16&lt;br /&gt;EVALUATION CONTEXT          14&lt;br /&gt;FUNCTION                   329&lt;br /&gt;INDEX                    22538&lt;br /&gt;INDEX PARTITION            617&lt;br /&gt;INDEXTYPE                   10&lt;br /&gt;JAVA CLASS               16417&lt;br /&gt;JAVA DATA                  306&lt;br /&gt;JAVA RESOURCE              770&lt;br /&gt;JOB                         18&lt;br /&gt;JOB CLASS                    2&lt;br /&gt;LIBRARY                    150&lt;br /&gt;LOB                        713&lt;br /&gt;LOB PARTITION                1&lt;br /&gt;MATERIALIZED VIEW            3&lt;br /&gt;OPERATOR                    57&lt;br /&gt;PACKAGE                    866&lt;br /&gt;PACKAGE BODY               807&lt;br /&gt;PROCEDURE                  252&lt;br /&gt;PROGRAM                     12&lt;br /&gt;QUEUE                       27&lt;br /&gt;RESOURCE PLAN                4&lt;br /&gt;RULE                         4&lt;br /&gt;RULE SET                    19&lt;br /&gt;SCHEDULE                     1&lt;br /&gt;SEQUENCE                  2759&lt;br /&gt;SYNONYM                  23307&lt;br /&gt;TABLE                    22631&lt;br /&gt;TABLE PARTITION            625&lt;br /&gt;TABLE SUBPARTITION          18&lt;br /&gt;TRIGGER                    565&lt;br /&gt;TYPE                      1941&lt;br /&gt;TYPE BODY                  174&lt;br /&gt;UNDEFINED                    6&lt;br /&gt;VIEW                      3804&lt;br /&gt;WINDOW                       2&lt;br /&gt;WINDOW GROUP                 1&lt;br /&gt;XML SCHEMA                  26&lt;br /&gt;&lt;br /&gt;43 filas seleccionadas.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Cambié la version del optimizador y el resultado fue el esperado, salió ordenado. Miremos el nuevo plan:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@TEST10G&gt;  explain plan for&lt;br /&gt;2  select object_type,count(1)&lt;br /&gt;3   from t&lt;br /&gt;4   group by object_type;&lt;br /&gt;&lt;br /&gt;Explicado.&lt;br /&gt;&lt;br /&gt;rop@TEST10G&gt; select * from table(dbms_xplan.display);&lt;br /&gt;&lt;br /&gt;PLAN_TABLE_OUTPUT&lt;br /&gt;----------------------------------------------------------------------------------------------------&lt;br /&gt;Plan hash value: 3156910365&lt;br /&gt;&lt;br /&gt;---------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |&lt;br /&gt;---------------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT   |      |    26 |   208 |   337   (6)| 00:00:05 |&lt;br /&gt;|   1 |  SORT GROUP BY     |      |    26 |   208 |   337   (6)| 00:00:05 |&lt;br /&gt;|   2 |   TABLE ACCESS FULL| T    | 99843 |   780K|   322   (2)| 00:00:04 |&lt;br /&gt;---------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;9 filas seleccionadas.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Ahora uso en antiguo path SORT GROUP BY y en consecuencia la salida no altero el orden&lt;br /&gt;Por ultimo, agregamos el order by y vemos como se agrega (ahora explicitamente) un path para ordenar el resultado antes de retornarlo.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@TEST10G&gt; ed&lt;br /&gt;Escrito file afiedt.buf&lt;br /&gt;&lt;br /&gt;1   explain plan for&lt;br /&gt;2  select object_type,count(1)&lt;br /&gt;3   from t&lt;br /&gt;4   group by object_type&lt;br /&gt;5*  order by object_type&lt;br /&gt;rop@TEST10G&gt; /&lt;br /&gt;&lt;br /&gt;Explicado.&lt;br /&gt;&lt;br /&gt;rop@TEST10G&gt; select * from table(dbms_xplan.display);&lt;br /&gt;&lt;br /&gt;PLAN_TABLE_OUTPUT&lt;br /&gt;----------------------------------------------------------------------------------------------------&lt;br /&gt;Plan hash value: 3861070257&lt;br /&gt;&lt;br /&gt;----------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation           | Name | Rows  | Bytes | Cost (%CPU)| Time     |&lt;br /&gt;----------------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT    |      |    26 |   208 |   354  (11)| 00:00:05 |&lt;br /&gt;|   1 |  SORT ORDER BY      |      |    26 |   208 |   354  (11)| 00:00:05 |&lt;br /&gt;|   2 |   HASH GROUP BY     |      |    26 |   208 |   354  (11)| 00:00:05 |&lt;br /&gt;|   3 |    TABLE ACCESS FULL| T    | 99843 |   780K|   324   (2)| 00:00:04 |&lt;br /&gt;----------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Este es un caso interesante de cambio de comportamiento que saca a la luz problemas de mala programación, que en versiones anteriores pasaban desapercibidas. Por tal motivo es muy importante tener código de calidad ,y que no solo funcione, para evitar sorpresas a futuro. En la próxima nota les voy a mostrar otro caso de cambio interesante, relacionado con cambios en el orden de evaluación de predicados.&lt;/valor&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5727738329548735676-5627592744758338011?l=oramdq.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://oramdq.blogspot.com/feeds/5627592744758338011/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://oramdq.blogspot.com/2009/09/cambios-de-comportamiento-tener-en.html#comment-form' title='1 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/5627592744758338011'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/5627592744758338011'/><link rel='alternate' type='text/html' href='http://oramdq.blogspot.com/2009/09/cambios-de-comportamiento-tener-en.html' title='Como minimizar problemas luego de un upgrade de versión de Oracle  (Caso1:  Ordenamiento implicito de GROUP BY en versiones anteriores a 10g R2)'/><author><name>Pablo A. Rovedo</name><uri>http://www.blogger.com/profile/02924687287216878757</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://3.bp.blogspot.com/_scsqOe7UOdc/SYDOV9ANLfI/AAAAAAAAAFQ/71M2XO1AZ1M/S220/DSC02067+(Small).JPG'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5727738329548735676.post-1372897398449384462</id><published>2009-08-21T12:27:00.000-07:00</published><updated>2009-08-24T07:57:03.162-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='PL/SQL'/><title type='text'>Instalación, Configuración y uso del paquete DBMS_PROFILER</title><content type='html'>En esta nota les voy a mostrar un poco acerca del paquete DBMS_PROFILER, que no es tan conocido, pero si se usa correctamente, puede ayudar a detectar en forma precisa en que lineas de código se encuentran las mayores demoras y asi focalizar la tarea de mejora de rendimiento de una aplicación o bloque de código PL/SQL.&lt;br /&gt;El paquete DBMS_PROFILER fue introducido en 8i y permite al equipo de desarrollo obtener detalle y comportamiento del código PL/SQL. No existe por default y hay que correr dos scripts para instalarlo. Por medio de esta herramienta los desarrolladores podrán obtener con granularidad de línea de código el detalle de ejecuciones, tiempo total, tiempo máximo, tiempo mínimo, etc.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Instalación y Configuración&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;La instalación consta de 2 pasos: La etapa 1 debe ser ejecutada por un DBA conectado como sys. Esta etapa crea el paquete. La etapa 2 no requiere privilegios especiales y puede ser ejecutada por el dueño (owner) en donde esta definido el modulo de código a instrumentar.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Paso 1&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Este paso crea el paquete DBMS_PROFILER en y realiza los grants y creación de sinónimo para poder ser utilizado desde cualquier usuario.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;CONNECT sys/password@service AS SYSDBA&lt;br /&gt;@$ORACLE_HOME/rdbms/admin/profload.sql&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Paso 2&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Este paso crea las 3 tablas y la secuencia necesarias para persistir la información recolectada por el DBMS_PROFILER.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;CONNECT profiler/profiler@service&lt;br /&gt;@$ORACLE_HOME/rdbms/admin/proftab.sql&lt;br /&gt;GRANT SELECT ON plsql_profiler_runnumber TO PUBLIC;&lt;br /&gt;GRANT SELECT, INSERT, UPDATE, DELETE ON plsql_profiler_data TO PUBLIC;&lt;br /&gt;GRANT SELECT, INSERT, UPDATE, DELETE ON plsql_profiler_units TO PUBLIC;&lt;br /&gt;GRANT SELECT, INSERT, UPDATE, DELETE ON plsql_profiler_runs TO PUBLIC;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Como Usar DBMS_PROFILER&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Para activar el profiler se debe encerrar la porción de código a evaluar por las siguientes sentencias:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Begin&lt;br /&gt;….&lt;br /&gt;dbms_profiler.start_profiler(&lt;etiqueta&gt;);&lt;br /&gt;…&lt;br /&gt;dbms_profiler.stop_profiler;&lt;br /&gt;…&lt;br /&gt;End;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Una vez ejecutado el modulo a analizar se deberá consultar en las tablas PLSQL_xxx para obtener los resultados.&lt;br /&gt;&lt;br /&gt;Ahora les voy a mostrar su uso con un ejemplo sencillo:&lt;br /&gt;&lt;br /&gt;Creamos un procedimiento sencillo que recorre un cursor implícito y va contando las iteraciones.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;create or replace procedure foo&lt;br /&gt;is&lt;br /&gt;l_cnt int := 0;&lt;br /&gt;begin&lt;br /&gt;dbms_profiler.start_profiler('Test');  -- Comienzo de traza&lt;br /&gt;for i in (select * from all_objects)&lt;br /&gt;loop&lt;br /&gt;   l_cnt := l_cnt+1;&lt;br /&gt;end loop;&lt;br /&gt;dbms_profiler.stop_profiler;           -- Fin de traza&lt;br /&gt;end;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Luego de ejecutarlo, verificamos los resultados con la siguiente consulta:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;SELECT u.runid,&lt;br /&gt;     u.unit_type,&lt;br /&gt;     u.unit_name,&lt;br /&gt;     d.line#,&lt;br /&gt;     s.text,&lt;br /&gt;     d.total_occur,&lt;br /&gt;     d.total_time,&lt;br /&gt;     d.min_time,&lt;br /&gt;     d.max_time&lt;br /&gt;FROM   plsql_profiler_units u,&lt;br /&gt;     plsql_profiler_data d,&lt;br /&gt;     user_source s&lt;br /&gt;WHERE  u.runid = d.runid AND&lt;br /&gt;     u.unit_number = d.unit_number AND&lt;br /&gt;     u.unit_name = s.name AND&lt;br /&gt;     d.line# = s.line AND&lt;br /&gt;     u.runid = 2&lt;br /&gt;ORDER BY u.unit_number, d.line#&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_scsqOe7UOdc/So79rGCTDJI/AAAAAAAAAHw/80VkV71f85k/s1600-h/dbms_profiler_table.bmp"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 400px; height: 54px;" src="http://4.bp.blogspot.com/_scsqOe7UOdc/So79rGCTDJI/AAAAAAAAAHw/80VkV71f85k/s400/dbms_profiler_table.bmp" border="0" alt=""id="BLOGGER_PHOTO_ID_5372510322296032402" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;La unidad de medida es en nanosegundos (10 elevado a la -9), para pasar a segundos hay que dividir por 1000 millones.&lt;br /&gt;En base a los resultados obtenidos se ve que demoró casi 3s en obtener los datos de las filas de la tabla (linea 6) y 0,03s en ejecutar la asignación (linea 8).&lt;br /&gt;&lt;/etiqueta&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5727738329548735676-1372897398449384462?l=oramdq.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://oramdq.blogspot.com/feeds/1372897398449384462/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://oramdq.blogspot.com/2009/08/instalacion-configuracion-y-uso-del.html#comment-form' title='1 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/1372897398449384462'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/1372897398449384462'/><link rel='alternate' type='text/html' href='http://oramdq.blogspot.com/2009/08/instalacion-configuracion-y-uso-del.html' title='Instalación, Configuración y uso del paquete DBMS_PROFILER'/><author><name>Pablo A. Rovedo</name><uri>http://www.blogger.com/profile/02924687287216878757</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://3.bp.blogspot.com/_scsqOe7UOdc/SYDOV9ANLfI/AAAAAAAAAFQ/71M2XO1AZ1M/S220/DSC02067+(Small).JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_scsqOe7UOdc/So79rGCTDJI/AAAAAAAAAHw/80VkV71f85k/s72-c/dbms_profiler_table.bmp' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5727738329548735676.post-2394083818982318339</id><published>2009-08-14T11:53:00.000-07:00</published><updated>2009-08-15T04:41:31.596-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='PL/SQL'/><category scheme='http://www.blogger.com/atom/ns#' term='Diagnóstico'/><category scheme='http://www.blogger.com/atom/ns#' term='Mantenimiento'/><title type='text'>Verificando el uso de "Buenas Practicas" en código PL/SQL</title><content type='html'>Para verificar si se aplican "Buenas Practicas" de programación y para contribuir a realizar código mas robusto y menos propenso a que se generen errores en tiempo de ejecución, a partir de 10g R1, se introdujo un nuevo mecanismo que permite advertir en tiempo de compilación sobre potenciales problemas (WARNINGS), que si bien dejan compilada la unidad de código, pueden darnos dolores de cabeza y conducir a que las aplicaciones que utilizan dicho código generen errores imprevistos o peor aún, que no se obtengan los datos correctos alterando la semántica pretendida y siendo, en muchas ocasiones, muy complicados de detectar.&lt;br /&gt;&lt;br /&gt;    Existe 4 categorias de WARNINGS:&lt;br /&gt;&lt;br /&gt;                   &lt;span style="font-weight: bold;"&gt; SEVERE  &lt;/span&gt;                :  Pueden causar acciones inesperadas, errores que hagan&lt;br /&gt;                                                         cancelar una operatoria o resultados erroneos.&lt;br /&gt;&lt;br /&gt;                    &lt;span style="font-weight: bold;"&gt;PERFORMANCE&lt;/span&gt;   :  Pueden causar problemas de rendimiento&lt;br /&gt;&lt;br /&gt;                   &lt;span style="font-weight: bold;"&gt; INFORMATIONAL&lt;/span&gt;:  No afectan el rendimiento ni altera los resultados pero&lt;br /&gt;                                                         advierte sobre complicaciones en el mantenimiento del&lt;br /&gt;                                                         codigo a futuro.&lt;br /&gt;&lt;br /&gt;                   &lt;span style="font-weight: bold;"&gt; ALL&lt;/span&gt;                           :  Contempla todos los casos anteriores.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Para activar los mensajes de warning se puede usar el parametro PLSQL_WARNINGS a nivel sesion o a nivel instancia (cosa que no recomiendo), tambien  se puede usar el paquete DBMS_WARNING para setear el nivel de warning deseado a nivel de código PL en procedures, packages, triggers, etc. Consultando  la vista [USER | ALL | DBA]_WARNING_SETTINGS se puede saber que objetos tienen activado el warning y consultando la vista [USER | ALL | DBA]_ERRORS, filtrando por el campo ATTRIBUTE='WARNING' se ven todos los warnings generados.&lt;br /&gt;&lt;br /&gt;Ahora que ya hice una introduccion rapida al tema, vayamos a los ejemplos:&lt;br /&gt;&lt;br /&gt;Habilito para detectar todas las categorias:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA10G&gt; alter session set plsql_warnings='ENABLE:ALL';&lt;br /&gt;&lt;br /&gt;Sesión modificada.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Creo una tabla sencila&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA10G&gt; create table t (x int,y varchar(5));&lt;br /&gt;&lt;br /&gt;Tabla creada.&lt;br /&gt;&lt;br /&gt;rop@DESA10G&gt; insert into t&lt;br /&gt;  2          select rownum,&lt;br /&gt;  3                 trunc(dbms_random.value(1,99999))&lt;br /&gt;  4          from dual&lt;br /&gt;  5          connect by rownum &lt;= 100000;&lt;br /&gt;&lt;br /&gt;100000 filas creadas.&lt;br /&gt;&lt;br /&gt;rop@DESA10G&gt; commit;&lt;br /&gt;&lt;br /&gt;Confirmación terminada.&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Voy a crear un procedimiento P_PRUEBA1 de forma tal de que se detecte un warning:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA10G&gt;ed&lt;br /&gt;  1  create or replace procedure p_prueba1 (p_val int)&lt;br /&gt;  2          is&lt;br /&gt;  3             l_cnt int;&lt;br /&gt;  4          begin&lt;br /&gt;  5              select count(1) into l_cnt&lt;br /&gt;  6              from t&lt;br /&gt;  7              where y = p_val;&lt;br /&gt;  8              if (l_cnt &gt; 0) then&lt;br /&gt;  9                   dbms_output.put_line ('El valor existe en la tabla');&lt;br /&gt; 10              else&lt;br /&gt; 11                   dbms_output.put_line ('El valor NO existe en la tabla');&lt;br /&gt; 12              end if;&lt;br /&gt; 13*         end;&lt;br /&gt;rop@DESA10G&gt; /&lt;br /&gt;&lt;br /&gt;SP2-0804: Procedimiento creado con advertencias de compilación&lt;br /&gt;&lt;br /&gt;rop@DESA10G&gt; select text from user_errors where name = 'P_PRUEBA1';&lt;br /&gt;&lt;br /&gt;TEXT&lt;br /&gt;----------------------------------------------------------------------------------------------------&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;PLW-07204: puede que la conversión que no sea de tipo de columna dé como resultado un plan de consulta subóptimo&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;En el caso de arriba detectó un potencial problema de performance, ya que al comparar la columna "y" de tipo varchar2 con el parametro "p_val" de tipo number se hace una conversión implicita TO_NUMBER() de la columna "y". Oracle siempre pasa a number cuando se comparan los tipos number y char/varchar2. &lt;br /&gt;Veamos otros ejemplos:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA10G&gt; ed&lt;br /&gt;Escrito file afiedt.buf&lt;br /&gt;&lt;br /&gt;  1  create or replace procedure p_prueba2 (p_val int)&lt;br /&gt;  2          is&lt;br /&gt;  3             l_cnt int;&lt;br /&gt;  4          begin&lt;br /&gt;  5              select count(1) into l_cnt&lt;br /&gt;  6              from t&lt;br /&gt;  7              where y = to_char(p_val);&lt;br /&gt;  8              if ( 0 = 0) then&lt;br /&gt;  9                 if (l_cnt &gt; 0) then&lt;br /&gt; 10                      dbms_output.put_line ('El valor existe en la tabla');&lt;br /&gt; 11                 else&lt;br /&gt; 12                      dbms_output.put_line ('El valor NO existe en la tabla');&lt;br /&gt; 13                 end if;&lt;br /&gt; 14              else&lt;br /&gt; 15                 null;&lt;br /&gt; 16              end if;&lt;br /&gt; 17*         end;&lt;br /&gt;rop@DESA10G&gt; /&lt;br /&gt;&lt;br /&gt;SP2-0804: Procedimiento creado con advertencias de compilación&lt;br /&gt;&lt;br /&gt;rop@DESA10G&gt; select text from user_errors where name = 'P_PRUEBA2';&lt;br /&gt;&lt;br /&gt;TEXT&lt;br /&gt;----------------------------------------------------------------------------------------------------&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;PLW-06002: Código inaccesible&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Este es una advertencia informativa. Ahora voy a crear una función:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA10G&gt; ed&lt;br /&gt;Escrito file afiedt.buf&lt;br /&gt;&lt;br /&gt;  1  create or replace function f_prueba1&lt;br /&gt;  2          return int&lt;br /&gt;  3  is&lt;br /&gt;  4    l_val int;&lt;br /&gt;  5    begin&lt;br /&gt;  6        l_val := dbms_random.value(1,10);&lt;br /&gt;  7*   end;&lt;br /&gt;rop@DESA10G&gt; /&lt;br /&gt;&lt;br /&gt;SP2-0806: Función creada con advertencias de compilación&lt;br /&gt;&lt;br /&gt;rop@DESA10G&gt; select text from user_errors where name = 'F_PRUEBA1';&lt;br /&gt;&lt;br /&gt;TEXT&lt;br /&gt;----------------------------------------------------------------------------------------------------&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;PLW-05005: la función F_PRUEBA1 se devuelve sin valor en la línea 7&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;al no retornar valor se podria generar un problema mas grave&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;rop@DESA10G&gt; ed&lt;br /&gt;Escrito file afiedt.buf&lt;br /&gt;&lt;br /&gt;  1  create or replace procedure p_prueba3 (p_val varchar2)&lt;br /&gt;  2          is&lt;br /&gt;  3          begin&lt;br /&gt;  4              insert into t (x) values (p_val);&lt;br /&gt;  5*         end;&lt;br /&gt;rop@DESA10G&gt; /&lt;br /&gt;&lt;br /&gt;SP2-0804: Procedimiento creado con advertencias de compilación&lt;br /&gt;&lt;br /&gt;rop@DESA10G&gt;  select text from user_errors where name = 'P_PRUEBA3';&lt;br /&gt;&lt;br /&gt;TEXT&lt;br /&gt;----------------------------------------------------------------------------------------------------&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;PLW-07202: el tipo de enlace daría como resultado una conversión lejos del tipo de columna&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;rop@DESA10G&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;El warning para el procedimiento P_PRUEBA3, aunque la traducción al español no es muy clara, da un posible problema en la conversión de tipos. Asi podriamos seguir probando otros tantos casos.&lt;br /&gt;La idea fue mostrarles que con esta herramienta se puede mejorar la calidad del software pl/sql y detectar en forma automatica y proactiva posibles problemas en tiempo de ejecución.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5727738329548735676-2394083818982318339?l=oramdq.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://oramdq.blogspot.com/feeds/2394083818982318339/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://oramdq.blogspot.com/2009/08/verificando-el-uso-de-buenas-practicas.html#comment-form' title='0 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/2394083818982318339'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5727738329548735676/posts/default/2394083818982318339'/><link rel='alternate' type='text/html' href='http://oramdq.blogspot.com/2009/08/verificando-el-uso-de-buenas-practicas.html' title='Verificando el uso de &quot;Buenas Practicas&quot; en código PL/SQL'/><author><name>Pablo A. Rovedo</name><uri>http://www.blogger.com/profile/02924687287216878757</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://3.bp.blogspot.com/_scsqOe7UOdc/SYDOV9ANLfI/AAAAAAAAAFQ/71M2XO1AZ1M/S220/DSC02067+(Small).JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5727738329548735676.post-3486312374275677815</id><published>2009-08-04T13:12:00.000-07:00</published><updated>2009-08-12T13:42:13.767-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='PL/SQL'/><category scheme='http://www.blogger.com/atom/ns#' term='Conceptos'/><title type='text'>Como afecta la frecuencia de commits en los procesos de carga masiva</title><content type='html'>Hace ya casi 7 años &lt;span id="SPELLING_ERROR_4" class="blsp-spelling-corrected"&gt;leí&lt;/span&gt; una nota en el sitio &lt;strong&gt;&lt;/strong&gt;&lt;a href="http://www.dbasupport.com/"&gt;http://www.dbasupport.com/&lt;/a&gt; , donde un "especialista" aconsejaba &lt;span id="SPELLING_ERROR_5" class="blsp-spelling-error"&gt;commitear&lt;/span&gt; con la mayor frecuencia posible, dando argumentos falsos que no &lt;span id="SPELLING_ERROR_6" class="blsp-spelling-error"&gt;resistian&lt;/span&gt; a la mas &lt;span id="SPELLING_ERROR_7" class="blsp-spelling-corrected"&gt;mínima&lt;/span&gt; prueba. En ese momento yo estaba leyendo un libro de &lt;span id="SPELLING_ERROR_8" class="blsp-spelling-error"&gt;Tom&lt;/span&gt; &lt;span id="SPELLING_ERROR_9" class="blsp-spelling-error"&gt;Kyte&lt;/span&gt; donde aconsejaba justamente lo contrario, por eso le &lt;span id="SPELLING_ERROR_10" class="blsp-spelling-error"&gt;postee&lt;/span&gt; una pregunta a &lt;span id="SPELLING_ERROR_11" class="blsp-spelling-error"&gt;Tom&lt;/span&gt; en su reconocido sitio asktom.oracle.com &lt;span id="SPELLING_ERROR_12" class="blsp-spelling-error"&gt;comentandole&lt;/span&gt; lo que &lt;span id="SPELLING_ERROR_13" class="blsp-spelling-error"&gt;habia&lt;/span&gt; &lt;span id="SPELLING_ERROR_14" class="blsp-spelling-error"&gt;leido&lt;/span&gt;, pueden leerla en: &lt;a style="color: rgb(255, 0, 0); font-weight: bold;" href="http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:4951966319022" rel="stylesheet"&gt;"&lt;span id="SPELLING_ERROR_15" class="blsp-spelling-error"&gt;Issue&lt;/span&gt; &lt;span id="SPELLING_ERROR_16" class="blsp-spelling-error"&gt;Frequent&lt;/span&gt; &lt;span id="SPELLING_ERROR_17" class="blsp-spelling-error"&gt;COMMIT&lt;/span&gt; &lt;span id="SPELLING_ERROR_18" class="blsp-spelling-error"&gt;Statements&lt;/span&gt;"&lt;/a&gt;, les aseguro que no tiene desperdicio. La verdad que nunca imaginé la &lt;span id="SPELLING_ERROR_19" class="blsp-spelling-error"&gt;repecución&lt;/span&gt; que tuvo eso, ya que el mismo &lt;span id="SPELLING_ERROR_20" class="blsp-spelling-error"&gt;Tom&lt;/span&gt; se comunicó con el autor de la nota y luego de unas idas y vueltas, el autor terminó admitiendo que lo que &lt;span id="SPELLING_ERROR_21" class="blsp-spelling-corrected"&gt;había&lt;/span&gt; escrito lo &lt;span id="SPELLING_ERROR_22" class="blsp-spelling-error"&gt;habia&lt;/span&gt; sacado de otro sitio y que &lt;span id="SPELLING_ERROR_23" class="blsp-spelling-error"&gt;analizandolo&lt;/span&gt; mejor &lt;span id="SPELLING_ERROR_24" class="blsp-spelling-corrected"&gt;coincidía&lt;/span&gt; que estaba mal. Luego este mismo caso fue &lt;span id="SPELLING_ERROR_25" class="blsp-spelling-error"&gt;referenciado&lt;/span&gt; por &lt;span id="SPELLING_ERROR_26" class="blsp-spelling-error"&gt;Tom&lt;/span&gt; en su libro "&lt;span id="SPELLING_ERROR_27" class="blsp-spelling-error"&gt;Effective&lt;/span&gt; &lt;span id="SPELLING_ERROR_28" class="blsp-spelling-error"&gt;Oracle&lt;/span&gt; &lt;span id="SPELLING_ERROR_29" class="blsp-spelling-error"&gt;by&lt;/span&gt; &lt;span id="SPELLING_ERROR_30" class="blsp-spelling-error"&gt;Design&lt;/span&gt;".&lt;br /&gt;&lt;br /&gt;Es importante destacar que &lt;span id="SPELLING_ERROR_31" class="blsp-spelling-corrected"&gt;además&lt;/span&gt; de la degradación de &lt;span id="SPELLING_ERROR_32" class="blsp-spelling-error"&gt;performance&lt;/span&gt;, producida por las esperas del tipo &lt;span id="SPELLING_ERROR_33" class="blsp-spelling-error"&gt;sync&lt;/span&gt; &lt;span id="SPELLING_ERROR_34" class="blsp-spelling-error"&gt;writes&lt;/span&gt; debido a la escritura necesaria en los &lt;span id="SPELLING_ERROR_35" class="blsp-spelling-error"&gt;log&lt;/span&gt; files cada vez que se realiza un &lt;span id="SPELLING_ERROR_36" class="blsp-spelling-error"&gt;commit&lt;/span&gt;, &lt;span id="SPELLING_ERROR_37" class="blsp-spelling-corrected"&gt;también&lt;/span&gt; se promueven los errores ORA-01555 y las posibles violaciones de integridad cuanto mas frecuentes sean los &lt;span id="SPELLING_ERROR_39" class="blsp-spelling-error"&gt;commits&lt;/span&gt;. En esta nota solo les voy a mostrar el impacto de la sobrecarga de &lt;span id="SPELLING_ERROR_40" class="blsp-spelling-error"&gt;commits&lt;/span&gt; en el rendimiento de la base y no en los otros problemas derivados.&lt;br /&gt;&lt;br /&gt;Voy a mostrarles con un ejemplo de prueba (&lt;span id="SPELLING_ERROR_0" class="blsp-spelling-error"&gt;Test&lt;/span&gt; Case) que cuanto menos &lt;span id="SPELLING_ERROR_1" class="blsp-spelling-error"&gt;commits&lt;/span&gt; se puedan efectuar mejor será la &lt;span id="SPELLING_ERROR_2" class="blsp-spelling-error"&gt;performance&lt;/span&gt; en los procesos de carga o &lt;span id="SPELLING_ERROR_3" class="blsp-spelling-error"&gt;actualización&lt;/span&gt; masiva de datos.&lt;br /&gt;&lt;br /&gt;Para realizar la prueba usé una base de datos &lt;span id="SPELLING_ERROR_41" class="blsp-spelling-corrected"&gt;versión&lt;/span&gt; 10g R2 y se armé un bloque &lt;span id="SPELLING_ERROR_42" class="blsp-spelling-error"&gt;pl&lt;/span&gt;/&lt;span id="SPELLING_ERROR_43" class="blsp-spelling-error"&gt;sql&lt;/span&gt; anónimo que &lt;span id="SPELLING_ERROR_44" class="blsp-spelling-corrected"&gt;esencialmente&lt;/span&gt; recorre un cursor &lt;span id="SPELLING_ERROR_45" class="blsp-spelling-error"&gt;autogenerado&lt;/span&gt; de 1 &lt;span id="SPELLING_ERROR_46" class="blsp-spelling-corrected"&gt;millón&lt;/span&gt; de registros con una &lt;span id="SPELLING_ERROR_47" class="blsp-spelling-error"&gt;transaccionabilidad&lt;/span&gt; definida (cantidad de &lt;span id="SPELLING_ERROR_48" class="blsp-spelling-error"&gt;commits&lt;/span&gt;). Para la prueba se realizaron 3 iteraciones por cada valor de prueba. El código utilizado fue el siguiente:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;declare&lt;br /&gt;l_time &lt;span id="SPELLING_ERROR_49" class="blsp-spelling-error"&gt;int&lt;/span&gt;;&lt;br /&gt;&lt;span id="SPELLING_ERROR_50" class="blsp-spelling-error"&gt;begin&lt;/span&gt;&lt;br /&gt;l_time := &lt;span id="SPELLING_ERROR_51" class="blsp-spelling-error"&gt;dbms&lt;/span&gt;_&lt;span id="SPELLING_ERROR_52" class="blsp-spelling-error"&gt;utility&lt;/span&gt;.&lt;span id="SPELLING_ERROR_53" class="blsp-spelling-error"&gt;get&lt;/span&gt;_time;&lt;br /&gt;&lt;span id="SPELLING_ERROR_54" class="blsp-spelling-error"&gt;  for&lt;/span&gt; i &lt;span id="SPELLING_ERROR_55" class="blsp-spelling-error"&gt;in&lt;/span&gt; (&lt;span id="SPELLING_ERROR_56" class="blsp-spelling-error"&gt;select&lt;/span&gt; &lt;span id="SPELLING_ERROR_57" class="blsp-spelling-error"&gt;rownum&lt;/span&gt; x,&lt;br /&gt;                &lt;span id="SPELLING_ERROR_58" class="blsp-spelling-error"&gt;dbms&lt;/span&gt;_&lt;span id="SPELLING_ERROR_59" class="blsp-spelling-error"&gt;random&lt;/span&gt;.&lt;span id="SPELLING_ERROR_60" class="blsp-spelling-error"&gt;string&lt;/span&gt;('a',10) y,&lt;br /&gt;                &lt;span id="SPELLING_ERROR_61" class="blsp-spelling-error"&gt;sysdate&lt;/span&gt;+(&lt;span id="SPELLING_ERROR_62" class="blsp-spelling-error"&gt;dbms&lt;/span&gt;_&lt;span id="SPELLING_ERROR_63" class="blsp-spelling-error"&gt;random&lt;/span&gt;.&lt;span id="SPELLING_ERROR_64" class="blsp-spelling-error"&gt;value&lt;/span&gt;(-200,200)) z&lt;br /&gt;         &lt;span id="SPELLING_ERROR_65" class="blsp-spelling-error"&gt;from&lt;/span&gt; dual&lt;br /&gt;         &lt;span id="SPELLING_ERROR_66" class="blsp-spelling-error"&gt;connect&lt;/span&gt; &lt;span id="SPELLING_ERROR_67" class="blsp-spelling-error"&gt;by&lt;/span&gt; &lt;span id="SPELLING_ERROR_68" class="blsp-spelling-error"&gt;rownum&lt;/span&gt; &lt;= 1000000)       &lt;span id="SPELLING_ERROR_69" class="blsp-spelling-error"&gt;&lt;br /&gt;loop&lt;/span&gt;          &lt;span id="SPELLING_ERROR_70" class="blsp-spelling-error"&gt;&lt;br /&gt;  insert&lt;/span&gt; &lt;span id="SPELLING_ERROR_71" class="blsp-spelling-error"&gt;into&lt;/span&gt; t &lt;span id="SPELLING_ERROR_72" class="blsp-spelling-error"&gt;values&lt;/span&gt; (i.x,i.y,i.z);          &lt;span id="SPELLING_ERROR_73" class="blsp-spelling-error"&gt;&lt;br /&gt;  if&lt;/span&gt; (&lt;span id="SPELLING_ERROR_74" class="blsp-spelling-error"&gt;mod&lt;/span&gt;(i.x,p_filas) = 0) &lt;span id="SPELLING_ERROR_75" class="blsp-spelling-error"&gt;then&lt;/span&gt;            &lt;span id="SPELLING_ERROR_76" class="blsp-spelling-error"&gt;&lt;br /&gt;     commit&lt;/span&gt;;          &lt;span id="SPELLING_ERROR_77" class="blsp-spelling-error"&gt;&lt;br /&gt;  end&lt;/span&gt; &lt;span id="SPELLING_ERROR_78" class="blsp-spelling-error"&gt;if&lt;/span&gt;;           &lt;span id="SPELLING_ERROR_79" class="blsp-spelling-error"&gt;&lt;br /&gt;end&lt;/span&gt; &lt;span id="SPELLING_ERROR_80" class="blsp-spelling-error"&gt;loop&lt;/span&gt;;&lt;br /&gt;dbms_output.put_line((dbms_utility.get_time-l_time)/100);&lt;br /&gt;&lt;span id="SPELLING_ERROR_81" class="blsp-spelling-error"&gt;end&lt;/span&gt;; &lt;/pre&gt;&lt;br /&gt;Donde el parámetro p_filas denota la cantidad de filas insertadas por cada &lt;span id="SPELLING_ERROR_82" class="blsp-spelling-error"&gt;commit&lt;/span&gt;. El proceso carga una tabla T con campos: x (&lt;span id="SPELLING_ERROR_83" class="blsp-spelling-error"&gt;number&lt;/span&gt;); y (&lt;span id="SPELLING_ERROR_84" class="blsp-spelling-error"&gt;varchar&lt;/span&gt;2) y z (date) con un cursor que genera valores aleatorios en el momento. Este tipo de cursor se creo para evitar cualquier tipo de &lt;span id="SPELLING_ERROR_85" class="blsp-spelling-error"&gt;buffering&lt;/span&gt; que pudiera darse si se inserta desde una tabla fuente, ya que haría que la prueba no sea aislada debido a que desde la primera iteración quedarán cacheados en algún &lt;span id="SPELLING_ERROR_86" class="blsp-spelling-error"&gt;substitema&lt;/span&gt; de disco o en el cache de &lt;span id="SPELLING_ERROR_87" class="blsp-spelling-error"&gt;Oracle&lt;/span&gt; las filas a insertar.&lt;br /&gt;&lt;br /&gt;Los resultados obtenidos fueron los descriptos en la siguiente tabla:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_scsqOe7UOdc/Snl4cH7qqFI/AAAAAAAAAG4/0j23paPYYaQ/s1600-h/tabla_commits.bmp"&gt;&lt;img style="margin: 0px auto 10px; text-align: center; width: 340px; display: block; height: 126px; cursor: pointer;" id="BLOGGER_PHOTO_ID_5366452855549634642" alt="" src="http://2.bp.blogspot.com/_scsqOe7UOdc/Snl4cH7qqFI/AAAAAAAAAG4/0j23paPYYaQ/s400/tabla_commits.bmp" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;En el &lt;span id="SPELLING_ERROR_88" class="blsp-spelling-corrected"&gt;gráfico&lt;/span&gt; de abajo se ve como van bajando los tiempos a medida que se dilata la ejecución del &lt;span id="SPELLING_ERROR_89" class="blsp-spelling-error"&gt;commits&lt;/span&gt;, es decir cuanto mas filas se procesan por cada &lt;span id="SPELLING_ERROR_90" class="blsp-spelling-error"&gt;commit&lt;/span&gt;:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_scsqOe7UOdc/Snl4wDyMRII/AAAAAAAAAHA/Be2ds7BNaec/s1600-h/commits.bmp"&gt;&lt;img style="margin: 0px auto 10px; text-align: center; width: 400px; display: block; height: 246px; cursor: pointer;" id="BLOGGER_PHOTO_ID_5366453198033536130" alt="" src="http://4.bp.blogspot.com/_scsqOe7UOdc/Snl4wDyMRII/AAAAAAAAAHA/Be2ds7BNaec/s400/commits.bmp" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Como conclusión final podemos ver que cuanto menos &lt;span id="SPELLING_ERROR_91" class="blsp-spelling-error"&gt;commits&lt;/span&gt; se realicen mejores serán los tiempos de procesamiento. Por lo tanto, siempre que el tamaño de los segmentos de &lt;span id="SPELLING_ERROR_92" class="blsp-spelling-error"&gt;UNDO&lt;/span&gt; estén debidamente estimados y que las reglas de negocio lo permitan es recomendable “&lt;span id="SPELLING_ERROR_93" class="blsp-spelling-error"&gt;commitear&lt;/span&gt;” lo menos &lt;span id="SPELLING_ERROR_94" class="blsp-spelling-error"&gt;frecuentemente&lt;/span&gt; posible.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5727738329548735676-3486312374275677815?l=oramdq.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://oramdq.blogspot.com/feeds/3486312374275677815/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://oramdq.blogspot.com/2009/08/como-afecta-la-frecuencia-de-commits-en.html#comment-form' title='2 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/57277383295
