<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://geeks.ms/utility/FeedStylesheets/rss.xsl" media="screen"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:wfw="http://wellformedweb.org/CommentAPI/"><channel><title>Surviving the Night : Rendimiento, SQL Server</title><link>http://geeks.ms/blogs/palvarez/archive/tags/Rendimiento/SQL+Server/default.aspx</link><description>Etiquetas: Rendimiento, SQL Server</description><dc:language>en</dc:language><generator>CommunityServer 2008.5 SP1 (Build: 31106.3070)</generator><item><title>Why Do I?: Memoria para los Workspaces en SQL Server (I)</title><link>http://geeks.ms/blogs/palvarez/archive/2012/12/30/why-do-i-memoria-para-los-workspaces-en-sql-server-i.aspx</link><pubDate>Sun, 30 Dec 2012 02:51:35 GMT</pubDate><guid isPermaLink="false">2a2e7ade-7474-448b-9de5-1515d8bb7d1b:207906</guid><dc:creator>Pablo Alvarez</dc:creator><slash:comments>2</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://geeks.ms/blogs/palvarez/rsscomments.aspx?PostID=207906</wfw:commentRss><comments>http://geeks.ms/blogs/palvarez/archive/2012/12/30/why-do-i-memoria-para-los-workspaces-en-sql-server-i.aspx#comments</comments><description>&lt;p&gt;Os voy a confesar un oscuro secreto: tengo un plan. Y no solo eso, sino que es un plan de los buenos. De los de Dominación Mundial™. Curiosamente, y como no podía se de otro modo, ese plan pasa por hacerme bueno, muy bueno en SQL Server&lt;sup&gt;1&lt;/sup&gt;, así que últimamente estoy aprovechando este descanso navideño para estudiar en profundidad algunos temas en los que nunca entre en suficiente detalle, o que tengo olvidados.&lt;/p&gt;  &lt;p&gt;Durante las sesiones de estudio me gusta tomar notas de lo que voy repasando, así como preparar unas &lt;em&gt;slides&lt;/em&gt; personales sobre el tema, que voy almacenando para futuras referencias y para dar charlas de esas que me gusta hacer de vez en cuando para satisfacer mi lado mas nerd. En esta ocasión, sin embargo, he pensado utilizar parte de esas notas para darle un poco mas de vidilla al blog y aprovechar para escribir de algunas partes de SQL Server de las que no hay demasiada literatura en nuestro idioma.&lt;/p&gt;  &lt;p&gt;En esta ocasión, voy a hablaros de una zona de memoria especial de SQL Server, llamada &lt;em&gt;Workspace&lt;/em&gt; y que me niego a traducir al castellano :) Mi objetivo para esta mini serie será explicaros algunos fenómenos perniciosos desde el punto de vista del rendimiento en SQL Server (como son &lt;em&gt;Hash Warnings&lt;/em&gt;, &lt;em&gt;Sort Warnings&lt;/em&gt;, etc…) y su relación con esta misteriosa y desconocida área.&lt;/p&gt;  &lt;p&gt;&lt;em&gt;NOTA: Tras muchos intentos en el pasado, soy plenamente consciente de que esta pequeña reactivación de mi blog va a tener un carácter muy temporal; después vendrá el día a día y se llevará todas mis buenas intenciones. Así que, tomaros esto como una pequeña aportación navideña, un regalo de reyes anticipado :P &lt;/em&gt;&lt;/p&gt;  &lt;h1&gt;&lt;/h1&gt;  &lt;h3&gt;La Memoria en SQL Server&lt;/h3&gt;  &lt;p&gt;Esta claro que antes de zambullirnos en la manera en la que SQL Server gestiona la memoria &lt;em&gt;workspace&lt;/em&gt;, es importante conocer el funcionamiento básico de la memoria en el producto. Para esta entrada tengo que hacer la suposición de que ya estáis familiarizados con la gestión de memoria básica de SQL Server. Algunas de las cosas básicas que debes de saber antes de continuar leyendo es que…&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;La memoria de SQL Server se divide en dos zonas principalmente:&lt;/li&gt;    &lt;ul&gt;     &lt;li&gt;&lt;strong&gt;Buffer Pool:&lt;/strong&gt; Es la caché principal de datos de SQL Server. Aquí se cachean, en páginas de 8Kb, datos, índices, planes de ejecución… Todas las reservas de 8Kb van aquí, y es sin duda la zona mas importante de memoria del producto.&lt;/li&gt;      &lt;li&gt;&lt;strong&gt;Non-Buffer Pool:&lt;/strong&gt; También llamada &lt;em&gt;mem-to-leave&lt;/em&gt;, es la zona de memoria en al que se realizan las reservas superiores a 8Kb.&lt;/li&gt;   &lt;/ul&gt;    &lt;li&gt;El parámetro de configuración &lt;b&gt;max server memory (MB)&lt;/b&gt; limita sólo Buffer Pool&lt;/li&gt;    &lt;li&gt;El tamaño de la región Non-Buffer Pool viene determinado por la siguiente formula: Memoria Física Total – (Max Server Memory + Memoria Física usada por el OS y otras aplicaciones)&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Si no es así, os recomiendo que busquéis alguna introducción básica al tema y luego continuéis leyendo por aquí :)&lt;/p&gt;  &lt;p&gt;&lt;em&gt;NOTA: Aunque estas cosas son bastante genéricas, en realidad solo son correctas para un SQL Server 2008 R2 con arquitectura x64.&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;Este maravilloso diagrama &lt;em&gt;“Windows 8 Modern UI”&amp;#160; &lt;/em&gt;que os he preparado, sin bordes, ni colorido, ni &lt;em&gt;ná de ná&lt;/em&gt;, muestra la relación entre los componentes de asignación de memoria en SQL Server 2008 R2:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_63E186F2.png"&gt;&lt;img title="image" style="border-top:0px;border-right:0px;background-image:none;border-bottom:0px;padding-top:0px;padding-left:0px;border-left:0px;display:inline;padding-right:0px;" border="0" alt="image" src="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_thumb_5F00_5E92A041.png" width="540" height="308" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Un rápido resumen podría ser el siguiente:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Las reservas de memoria de 8Kb o menores se almacenan en el Buffer Pool (mediante en &lt;em&gt;single-page allocator&lt;/em&gt;)&lt;/li&gt;    &lt;li&gt;Las reservas de memoria mayores de 8Kb se dirigen al área mal llamada &lt;em&gt;Mem-to-leave &lt;/em&gt;a través del &lt;em&gt;multi-page allocator&lt;/em&gt;.&lt;/li&gt;    &lt;li&gt;Las reservas de memoria del código de CLR que se ejecuta dentro del proceso de SQL Server se dirigen también al &lt;em&gt;mem-to-leave &lt;/em&gt;mediante el &lt;em&gt;VAS allocator&lt;/em&gt;.&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;En SQL Server 2012 se han producido bastantes cambios a la gestión de memoria: sin duda, el más importante es que ya no se realiza una diferencia entre el &lt;em&gt;single-page allocator&lt;/em&gt; y el &lt;em&gt;multi-page allocator, &lt;/em&gt;habiendo sido sustituidos ambos por un único y genérico gestor de reservas de memoria, como se puede ver en el siguiente diagrama:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_0F45A7ED.png"&gt;&lt;img title="image" style="border-top:0px;border-right:0px;background-image:none;border-bottom:0px;padding-top:0px;padding-left:0px;border-left:0px;display:inline;padding-right:0px;" border="0" alt="image" src="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_thumb_5F00_55566500.png" width="540" height="308" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;A partir de ahora, tanto las reservas tradicionales menores de 8Kb, como las reservas superiores a 8Kb y las reservas del CLR pasan a estar en los limites de memoria definidos entre el &lt;b&gt;max server memory (MB)&lt;/b&gt; y el &lt;b&gt;min server memory (MB)&lt;/b&gt;. &lt;/p&gt;  &lt;p&gt;&lt;em&gt;NOTA: Si habéis hecho una actualización a SQL Server 2012 sin tener en cuenta este cambio, es probable que queráis repasar vuestra configuración de memoria después de leer esto. Tened en cuenta también que que muchas DMVs han cambiado, por lo que scripts de captura y monitorización pueden estar devolviéndoos resultados no fiables.&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;Una vez descritas las dos grandes zonas de memoria de SQL Server, estoy seguro de que muchos de vosotros ya estáis poniendo en su lugar, dentro de la zona controlada por el &lt;em&gt;Memory Manager&lt;/em&gt; (la zona verde), las varias &lt;em&gt;caches &lt;/em&gt;de datos que conocéis en SQL Server: para los datos recientemente usados, para los planes de ejecución, etc. Sin embargo, SQL Server tiene mas usos para la memoria principal, y uno de ellos es para la…. ¡¡Ejecución de Consultas!! &lt;/p&gt;  &lt;p&gt;&lt;em&gt;&amp;lt;sonido de truenos y lluvia distante, por favor….. …… ¡gracias!&amp;gt;&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;Ok… ok… se que era predecible :) Pero es un paso importante para llegar a donde os quiero llevar. Vamos a hablar de unas pequeñas estructuras muy amigables que viven de de nuestros planes de ejecución.&lt;/p&gt;  &lt;h3&gt;Los Iteradores&lt;/h3&gt;  &lt;p&gt;En realidad, los iteradores (u operadores, pero como en realidad todos son iteradores, prefiero llamarlos asi) son las piezas de lego con las que se construyen nuestros planes de ejecución. Hay aproximadamente un centenar de ellos en SQL Server 2012, y nos sirven para realizar acciones tan variadas como leer registros, realizar filtrados y agregados, actualizar datos, etc… Estos se combinan y son los que finalmente dan lugar a nuestros planes de ejecución.&lt;/p&gt;  &lt;p&gt;A nivel interno, todos exponen los mismos tres métodos:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;     &lt;p&gt;&lt;strong&gt;Init()&lt;/strong&gt;: Instancia el operador físico e inicializa todos los recursos que pueda necesitar. Idealmente solo se ejecutará una vez, pero puede ser necesario llamarle múltiples veces: esto suele indicar algún problema y se materializa con un número alto de &lt;em&gt;binds/rebinds&lt;/em&gt; en el plan de ejecución.&lt;/p&gt;   &lt;/li&gt;    &lt;li&gt;     &lt;p&gt;&lt;strong&gt;GetNext()&lt;/strong&gt;: La llamada a este método devuelve la primera fila, o la fila siguiente a la actual. El operador puede recibir cualquier número de llamadas a este método. ¿Entendéis ahora porque llamamos iteradores a todos los operadores? ;)&lt;/p&gt;   &lt;/li&gt;    &lt;li&gt;     &lt;p&gt;&lt;strong&gt;Close()&lt;/strong&gt;: Este método provoca la ejecución de las tareas de limpieza y eliminación de recursos del operador. Solo puede ser invocado una vez.&lt;/p&gt;   &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Aunque a primera vista parece extraño que *todos* los operadores se implementen básicamente con un GetNext(), esta aproximación hace que sea relativamente fácil implementar nuevos operadores para el Query Optimizer/Query Processor.&lt;/p&gt;  &lt;p&gt;&lt;em&gt;NOTA: Aquí tenéis la referencia de los disponibles en SQL Server 2012: &lt;/em&gt;&lt;a title="http://msdn.microsoft.com/es-es/library/ms191158.aspx" href="http://msdn.microsoft.com/es-es/library/ms191158.aspx"&gt;&lt;em&gt;http://msdn.microsoft.com/es-es/library/ms191158.aspx&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Sigamos: &lt;strong&gt;todos&lt;/strong&gt; los iteradores (&lt;em&gt;Table Scan&lt;/em&gt;, &lt;em&gt;index Scan, Compute Scalar&lt;/em&gt;, etc…) en un plan de ejecución necesitan &lt;em&gt;algo&lt;/em&gt; de memoria para levantar su infraestructura básica, para mantener el estado del iterador y para &lt;strong&gt;almacenar&lt;/strong&gt; &lt;strong&gt;las filas que están siendo procesadas&lt;/strong&gt;. Este último punto es el más interesante, veamos por qué.&lt;/p&gt;  &lt;p&gt;Los iteradores pueden ser de dos tipos:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;La mayoría de los iteradores que nos vamos a encontrar en un plan de ejecución son de tipo &lt;strong&gt;streaming&lt;/strong&gt;; esto es, pedirán una fila, la procesarán y pedirán la siguiente. Evidentemente, en estos escenarios el consumo de memoria del iterador para almacenar las filas que están siendo procesadas será escaso y predecible: siempre se almacena una única fila.&lt;/li&gt;    &lt;li&gt;Hay unos pocos operadores que son de tipo &lt;strong&gt;parcialmente&lt;/strong&gt; o &lt;strong&gt;completamente&lt;/strong&gt; &lt;strong&gt;bloqueantes&lt;/strong&gt; (&lt;strong&gt;blocking&lt;/strong&gt;). Estos deben de mantener un subconjunto (o la totalidad) de las filas en memoria hasta que finaliza su proceso, en cuyo momento el &lt;strong&gt;GetNext()&lt;/strong&gt; devuelve un bloque de &lt;em&gt;n&lt;/em&gt; filas, en lugar de una única filas.&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Evidentemente los iteradores parcialmente o completamente bloqueantes consumen mucha más memoria. Un ejemplo básico es el operador &lt;strong&gt;sort&lt;/strong&gt;, que es &lt;strong&gt;complentamente bloqueante&lt;/strong&gt; ya que requiere que todas las filas se almacenen hasta la llegada de la última fila de su operador de entrada, antes de empezar a producir resultados por su salida. Esto es así, porque la última fila en entrar podría ordenarse la primera; es un ejemplo muy claro.&lt;/p&gt;  &lt;p&gt;La buena noticia es que son muy pocos los operadores bloqueantes, y se pueden categorizar en estos tres tipos:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;&lt;strong&gt;Hash Match:&lt;/strong&gt;&lt;/li&gt;    &lt;ul&gt;     &lt;li&gt;Hash Match&lt;/li&gt;      &lt;li&gt;Hash Match (Aggregate)&lt;/li&gt;   &lt;/ul&gt;    &lt;li&gt;&lt;strong&gt;Sort:&lt;/strong&gt;&lt;/li&gt;    &lt;ul&gt;     &lt;li&gt;Sort&lt;/li&gt;      &lt;li&gt;Sort (Top N Sort)&lt;/li&gt;   &lt;/ul&gt;    &lt;li&gt;&lt;strong&gt;Exchange:&lt;/strong&gt;&lt;/li&gt;    &lt;ul&gt;     &lt;li&gt;Distribute Streams&lt;/li&gt;      &lt;li&gt;Gather Streams&lt;/li&gt;      &lt;li&gt;Repartition Streams&lt;/li&gt;   &lt;/ul&gt; &lt;/ul&gt;  &lt;p&gt;Cuando una consulta utiliza este tipo de operadores, tiene que realizar peticiones de memoria especiales a SQL Server para poder realizar las tareas que estos iteradores requieren: estas peticiones de memoria son las que definen el &lt;em&gt;workspace&lt;/em&gt; de la consulta (&lt;em&gt;workspace memory grants&lt;/em&gt;).&lt;/p&gt;  &lt;h3&gt;Algunas Preguntas&lt;/h3&gt;  &lt;p&gt;Lo primero que nos podemos preguntar es, &lt;strong&gt;¿de dónde sacamos esa memoria?&lt;/strong&gt; Esta pregunta es la fácil; estoy convencido de que todos ya sabéis que viene del Buffer Pool… entre otras cosas porque ya lo hemos comentado antes ;)&lt;/p&gt;  &lt;p&gt;La siguiente pregunta es: &lt;strong&gt;¿cuanta memoria se va a reservar de nuestro Buffer Pool para la ejecución de la consulta?&lt;/strong&gt; De esto se encargará nuestro amigo, el &lt;em&gt;Query Optimizer&lt;/em&gt;, que tratará de averiguar el &lt;em&gt;Ideal Grant&lt;/em&gt;, es decir, la reserva de memoria total para el plan de ejecución que permitirá que todos los operadores puedan ejecutarse completamente en memoria. Para realizar este calculo del &lt;em&gt;Ideal Grant&lt;/em&gt; se utilizarán las estadísticas (histograma de distribución de los valores de cada columna), en concreto el número de filas, y el tamaño por cada fila.&lt;/p&gt;  &lt;p&gt;Es interesante aquí darse cuenta de que estamos trabajando con &lt;strong&gt;estimaciones&lt;/strong&gt;. A modo de ejemplo, supongamos un escenario de una consulta simple con un ORDER BY: el &lt;em&gt;Query Optimizer&lt;/em&gt; sabe que ese operador necesitara “estimated rows * estimated row size” bytes. ¿porque decimos que son estimados? Bien, imaginaros que el tipo de datos es NVARCHAR(100)… realmente no sabemos lo que ocupa esa columna en cada fila, solo tenemos una estimación.&lt;/p&gt;  &lt;p&gt;Para ver la información de la memoria asignada al &lt;em&gt;workspace&lt;/em&gt; de una consulta desde el SSMS (y sin tener que recurrir a leer el XML a mano), podemos simplemente mirar las propiedades del operador principal (el SELECT en el ejemplo), y veremos un nodo llamada &lt;em&gt;MemoryGrantInfo&lt;/em&gt;:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_5411CC21.png"&gt;&lt;img title="image" style="border-top:0px;border-right:0px;background-image:none;border-bottom:0px;padding-top:0px;padding-left:0px;margin:0px;border-left:0px;display:inline;padding-right:0px;" border="0" alt="image" src="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_thumb_5F00_7DA59754.png" width="540" height="362" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;SQL Server impone un limite máximo a la memoria destinada a los &lt;em&gt;workspaces&lt;/em&gt; (sobre el 75% del Buffer Pool), para evitar que una consulta extremadamente pesada impida la correcta ejecución de consultas mucho mas livianas y rápidas en el servidor. &lt;/p&gt;  &lt;p&gt;Esto puede parecer mucha memoria, pero imaginaros que tenéis un Data Warehouse con una tabla de hechos que os pesa 500 Gb, y queréis realizar algo ‘tan simple’ como un ‘SELECT * FROM FactTable ORDER BY Date’, ¿Podría vuestro servidor mantener todo el operador en memoria? ¿que sucedería?&lt;/p&gt;  &lt;p&gt;Las respuestas, ¡en la próxima entrada! ;)&lt;/p&gt;  &lt;h3&gt;Resumiendo&lt;/h3&gt;  &lt;p&gt;&lt;font style="background-color:#ffffff;"&gt;&lt;/font&gt;&lt;font style="background-color:#ffff00;"&gt;&lt;/font&gt;&lt;font&gt;Buff.. ¡no ha estado nada mal! En una única entrada hemos hablado de la gestión de memoria en SQL Server, de qué son y como funcionan los iteradores y hemos introducido el concepto de iteradores bloqueantes y sus &lt;em&gt;workspaces&lt;/em&gt; asociados. Nos queda mucho por ver aún; de hecho, aún no os he presentado ni siquiera los problemas que queremos resolver, pero no he podido evitar la tentación de aprovechar esta mini serie para ir introduciendo algunos elementos de SQL Server de los que no solemos hablar.&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;Por cierto, si has llegado hasta aquí, ¡enhorabuena! Definitivamente debes de tener interés en SQL Server o mucho tiempo libre, ya que admito que no es el tema más ameno y ligero para una lectura en temporada navideña. &lt;/p&gt;  &lt;h5&gt;&lt;em&gt;&lt;sup&gt;&lt;font style="font-weight:normal;"&gt;1&lt;/font&gt;&lt;/sup&gt;&lt;font style="font-weight:normal;"&gt;: Lo siento, pero no puedo explicaros la razón por la que un plan de Dominación Mundial™ requiere un conocimiento extremo las tripas de SQL Server. Bueno… en realidad si que podría explicarlo, pero tendría que mataros después. Y la verdad es que ya os he cogido cariño y esas cosas, así qué mejor no preguntéis :)&lt;/font&gt;&lt;/em&gt;&lt;/h5&gt;  &lt;h5&gt;Rock Tip:&lt;/h5&gt;  &lt;p&gt;Si en &lt;a href="http://geeks.ms/blogs/palvarez/archive/2012/11/21/brothers-in-arms-we-are-hiring.aspx"&gt;la última entrada&lt;/a&gt; os ponía un temazo de W.E.T., en esta ocasión no me voy a ir muy lejos: &lt;a href="http://www.youtube.com/watch?v=5dYJE352Kco"&gt;‘Why do I?’&lt;/a&gt; es el tema que abre &lt;strong&gt;Artwork&lt;/strong&gt;, el disco de debut de los suecos &lt;a href="http://www.woa.se/"&gt;Work of Art&lt;/a&gt;. I digo que no me voy muy lejos, ya que el guitarrista, principal compositor y lider de esta banda no es otro que &lt;strong&gt;Robert Säll&lt;/strong&gt;, uno de los lideres de W.E.T. de quien ya os hablaba anteriormente.&lt;/p&gt;  &lt;p&gt;¿Que deciros de esta banda? Una de las mayores y más agradables sorpresas de mi vida musical: hablamos de un hard rock melódico de mucha, mucha clase, estilo y elegancia. Si duda una de mis bandas favoritas del momento, aunque he de reconocer que les tengo un cariño especial y poco objetivo. Decidieron hacer el primer concierto de su historia en Madrid, en la venerable sala Ritmo &amp;amp; Compás, y os puedo decir que todos los que tuvimos la suerte de vivir ese momento lo vamos a guardar como uno de los conciertos de nuestras vidas.&lt;/p&gt;      &lt;p&gt;&lt;em&gt;Keep Rockin’!&lt;/em&gt;&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://geeks.ms/aggbug.aspx?PostID=207906" width="1" height="1"&gt;</description><category domain="http://geeks.ms/blogs/palvarez/archive/tags/SQL+Server/default.aspx">SQL Server</category><category domain="http://geeks.ms/blogs/palvarez/archive/tags/Rendimiento/default.aspx">Rendimiento</category></item><item><title>Hot: Inserciones Masivas en MongoDB vs SQL Server</title><link>http://geeks.ms/blogs/palvarez/archive/2011/11/17/hot-inserciones-masivas-en-mongodb-vs-sql-server.aspx</link><pubDate>Thu, 17 Nov 2011 02:09:00 GMT</pubDate><guid isPermaLink="false">2a2e7ade-7474-448b-9de5-1515d8bb7d1b:201723</guid><dc:creator>Pablo Alvarez</dc:creator><slash:comments>11</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://geeks.ms/blogs/palvarez/rsscomments.aspx?PostID=201723</wfw:commentRss><comments>http://geeks.ms/blogs/palvarez/archive/2011/11/17/hot-inserciones-masivas-en-mongodb-vs-sql-server.aspx#comments</comments><description>&lt;p&gt;Esta semana estoy en San Sebastián, impartiendo una pequeña formación sobre optimización de SQL Server. A priori, la semana se presentaba tranquila y con gran carga turística al tratarse de la primera ocasión que visito esta ciudad, y mi predilección general por estas tierras del norte. &lt;/p&gt;  &lt;p&gt;Lamentablemente, todos mis planes se torcieron tan pronto como descubrí que &lt;strong&gt;&lt;a href="http://geeks.ms/blogs/unai/"&gt;Unai&lt;/a&gt;&lt;/strong&gt; estaba en la misma ciudad que yo. Como no podía ser de otro modo, ente &lt;em&gt;pintxo&lt;/em&gt; y &lt;em&gt;pintxo&lt;/em&gt; nuestros temas técnicos favoritos empezaron a surgir en nuestras conversaciones. Y, como cualquiera que conozca a nuestro &lt;em&gt;Unai &lt;/em&gt;podrá intuir, no pasó demasiado tiempo antes de que MongoDB secuestrara todos nuestros hilos conversacionales y se convirtiera en nuestro particular &lt;em&gt;trending topic&lt;/em&gt;, por delante de otras apasionantes cuestiones como la entropía de la demografía en el postmoderno barroco, o el peculiar criterio que tengo a la hora de escoger &lt;a href="http://img.guitarchina.com/imgeshop/ibanez/rg3550dy01.jpg"&gt;mis guitarras&lt;/a&gt;.&lt;/p&gt;  &lt;p&gt;He de confesar que no recuerdo muy bien la cadena de acontecimientos que nos condujo a la historia que os voy a contar; quizá fuera mi falta de conocimiento respecto a este producto tan de moda (MongoDB), o el número de &lt;em&gt;zuritos&lt;/em&gt; que nos habíamos tomado… No lo acabo de tener muy claro, pero el hecho es que, de algún modo, me metí de lleno en un juego/apuesta que acabaría con el escaso tiempo libre que me quedaba a lo largo de la semana.&lt;/p&gt;  &lt;h3&gt;&lt;/h3&gt;  &lt;h3&gt;&lt;/h3&gt;  &lt;h3&gt;La Apuesta&lt;/h3&gt;  &lt;p&gt;Las reglas eran muy sencillas: el ganador sería aquel de nosotros dos que fuera capaz de insertar una carga de trabajo acordada previamente en menos tiempo. Él con su MongoDB de marras, y yo con mi viejo, fiable, pero no siempre bien ponderado SQL Server 2008 R2, ¡como no!&lt;/p&gt;  &lt;p&gt;Tras duras negociaciones de paz, &lt;em&gt;Unai&lt;/em&gt; y yo llegamos a un acuerdo para evitar emplear armas de destrucción masiva; por este motivo finalmente decidimos no evaluar la que es, de lejos, la manera más rápida de insertar datos en SQL Server: las &lt;em&gt;Bulk Inserts&lt;/em&gt;.&lt;/p&gt;  &lt;p&gt;La carga de trabajo que nos pusimos de objetivo fue 500.000 registros, cada uno conteniendo un GUID y 20 caracteres textuales (no unicode).&lt;/p&gt;  &lt;p&gt;&lt;em&gt;&lt;strong&gt;Descargo de Responsabilidad:&lt;/strong&gt; Siempre que escribo acerca de una tecnología ajena al ecosistema de Microsoft acabo con problemas de fanatismos y guerras de religión. Por ello, dejadme que deje muy claro que este post solo pretende contar la historia de un pique interesante con un compañero, así como tratar de aprovechar la ocasión para comentaros un par de técnicas no triviales para realizar cargas rápidas de datos en SQL Server. Vaya por delante que MongoDB me resulta un producto más que interesante y con escenarios para los que es más idóneo que una base de datos relacional.&lt;/em&gt;&lt;/p&gt;  &lt;h3&gt;Construcción del Entorno de Prueba&lt;/h3&gt;  &lt;p&gt;En entorno de prueba inicial consistió simplemente en una base de datos que contiene la tabla sobre la que vamos a insertar nuestros 500.000 registros. La única preparación inicial que he realizado de cara a mejorar un poco el rendimiento ha sido dimensionar de modo apropiado los ficheros (para evitar autocrecimientos por uso del log de transacciones en las inserciones) y establecer el modelo de recuperación a SIMPLE.&lt;/p&gt;  &lt;p&gt;A continuación os dejo el script inicial, aunque como veréis más adelante hemos realizado algún que otro cambio:&lt;/p&gt;  &lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;CREATE&lt;/span&gt; &lt;span class="kwrd"&gt;DATABASE&lt;/span&gt; DemoInserts &lt;span class="kwrd"&gt;ON&lt;/span&gt; &lt;span class="kwrd"&gt;PRIMARY&lt;/span&gt; 
( 
    NAME = &lt;span class="str"&gt;&amp;#39;DemoInserts&amp;#39;&lt;/span&gt;, 
    FILENAME = &lt;span class="str"&gt;&amp;#39;c:\Databases\DemoInserts.MDF&amp;#39;&lt;/span&gt;, 
    &lt;span class="kwrd"&gt;SIZE&lt;/span&gt; = 200, 
    FILEGROWTH = 10% 
) 
LOG &lt;span class="kwrd"&gt;ON&lt;/span&gt; 
( 
    NAME = N&lt;span class="str"&gt;&amp;#39;DemoInserts_Log&amp;#39;&lt;/span&gt;, 
    FILENAME = N&lt;span class="str"&gt;&amp;#39;c:\Databases\DemoInserts_Log.LDF&amp;#39;&lt;/span&gt;, 
    &lt;span class="kwrd"&gt;SIZE&lt;/span&gt; = 200, 
    FILEGROWTH = 10% 
) 
&lt;span class="kwrd"&gt;GO&lt;/span&gt; 
&lt;span class="kwrd"&gt;ALTER&lt;/span&gt; &lt;span class="kwrd"&gt;DATABASE&lt;/span&gt; DemoInserts &lt;span class="kwrd"&gt;SET&lt;/span&gt; RECOVERY SIMPLE 
&lt;span class="kwrd"&gt;GO&lt;/span&gt; 
&lt;span class="kwrd"&gt;USE&lt;/span&gt; DemoInserts 
&lt;span class="kwrd"&gt;GO&lt;/span&gt; 
&lt;span class="kwrd"&gt;CREATE&lt;/span&gt; &lt;span class="kwrd"&gt;TABLE&lt;/span&gt; [Test] 
( 
    ID UNIQUEIDENTIFIER &lt;span class="kwrd"&gt;NOT&lt;/span&gt; &lt;span class="kwrd"&gt;NULL&lt;/span&gt; &lt;span class="kwrd"&gt;DEFAULT&lt;/span&gt; NEWID(), 
    Payload &lt;span class="kwrd"&gt;VARCHAR&lt;/span&gt;(20) &lt;span class="kwrd"&gt;NULL&lt;/span&gt;, 
)&lt;/pre&gt;


&lt;p&gt;&amp;#160;&lt;/p&gt;

&lt;h3&gt;¡Vamos a Insertar Filas!&lt;/h3&gt;

&lt;h3&gt;&lt;/h3&gt;

&lt;p&gt;La primera solución consistirá en un simple bucle de 500.000 ejecuciones, en el que para cada iteración insertamos una fila. Su única ‘optimización’ fue el empleo de la opción NCOUNT ON para evitar saturar el output buffer con los típicos mensajes de (1 row affected),&amp;#160; tal como se puede ver en el siguiente fragmento de código:&lt;/p&gt;

&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;SET&lt;/span&gt; NOCOUNT &lt;span class="kwrd"&gt;ON&lt;/span&gt;

&lt;span class="kwrd"&gt;DECLARE&lt;/span&gt; @cont &lt;span class="kwrd"&gt;INT&lt;/span&gt; 
&lt;span class="kwrd"&gt;SET&lt;/span&gt; @cont = 0

&lt;span class="kwrd"&gt;WHILE&lt;/span&gt; (@cont &amp;lt; 500000) 
&lt;span class="kwrd"&gt;BEGIN&lt;/span&gt; 
   INSERT &lt;span class="kwrd"&gt;INTO&lt;/span&gt; Test(ID, Payload) &lt;span class="kwrd"&gt;VALUES&lt;/span&gt; (&lt;span class="kwrd"&gt;DEFAULT&lt;/span&gt;, REPLICATE(&lt;span class="str"&gt;&amp;#39;X&amp;#39;&lt;/span&gt;, 20)) 
   &lt;span class="kwrd"&gt;SET&lt;/span&gt; @cont = @cont + 1 
&lt;span class="kwrd"&gt;END&lt;/span&gt;&lt;/pre&gt;


&lt;p&gt;El tiempo de ejecución de la inserción en mi equipo fue de &lt;strong&gt;3 minutos y 8 segundos.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;NOTA:&lt;/strong&gt; Todos los tiempos tomados en este artículo se han calculado realizando el promedio de tres ejecuciones sucesivas.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Evidentemente, desde antes de realizar esta prueba yo era consciente de que este mecanismo no iba a arrojar buenos resultados, pero me serviría para tener una línea base respecto a la que poder evaluar las mejoras posteriores y para hacerme una idea respecto a lo que podría esperar de SQL Server. &lt;/p&gt;

&lt;p&gt;Llegados a este punto le comenté a Unai que me daría con un canto en los dientes si era capaz de bajar mi tiempo de inserción por debajo de los 20 segundos. Mi amigo y competidor me corrigió, asegurándome que sería él mismo quien se encargaría de darme con el susodicho canto en toda mi dentadura si conseguía tal hazaña. Creo que en ese momento empecé a tomarme nuestra pequeña competición en serio ;)&lt;/p&gt;

&lt;h3&gt;Manos a la Obra… ¿Dónde esta mi cuello de botella?&lt;/h3&gt;

&lt;p&gt;Siempre hay un cuello de botella; ¡siempre! Aunque el rendimiento sea excelente, siempre hay un factor limitante, y solo uno. En este caso, un vistazo rápido al &lt;em&gt;Task Manager &lt;/em&gt;me permitió ver que la CPU no eran en este caso el problema:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_465CBF54.png"&gt;&lt;img style="background-image:none;border-bottom:0px;border-left:0px;padding-left:0px;padding-right:0px;display:block;float:none;margin-left:auto;border-top:0px;margin-right:auto;border-right:0px;padding-top:0px;" title="image" border="0" alt="image" src="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_thumb_5F00_387A538C.png" width="444" height="323" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Esto me dio una buena idea para el siguiente intento: realizar una pequeña aplicación en C# que se encargara de lanzar INSERTS modo masivo en paralelo, usando múltiples hilos, para tratar de aprovechar al máximo las cuatro CPUs de las que dispone mi equipo de pruebas.&lt;/p&gt;

&lt;p&gt;A continuación os pongo el código exacto que he empleado para esta prueba, aunque casi me da vergüenza ;) No me juzguéis por él, es solo un ejemplo &lt;em&gt;‘quick and dirty’&lt;/em&gt;!:&lt;/p&gt;

&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;using&lt;/span&gt; System;
&lt;span class="kwrd"&gt;using&lt;/span&gt; System.Collections.Generic;
&lt;span class="kwrd"&gt;using&lt;/span&gt; System.Linq;
&lt;span class="kwrd"&gt;using&lt;/span&gt; System.Text;
&lt;span class="kwrd"&gt;using&lt;/span&gt; System.Threading.Tasks;
&lt;span class="kwrd"&gt;using&lt;/span&gt; System.Data.SqlClient;
&lt;span class="kwrd"&gt;using&lt;/span&gt; System.Diagnostics;

&lt;span class="kwrd"&gt;namespace&lt;/span&gt; ConsoleApplication5
{
    &lt;span class="kwrd"&gt;class&lt;/span&gt; Program
    {

        &lt;span class="kwrd"&gt;static&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; Main(&lt;span class="kwrd"&gt;string&lt;/span&gt;[] args)
        {
            Stopwatch watch = Stopwatch.StartNew();

            var mainTask = &lt;span class="kwrd"&gt;new&lt;/span&gt; TaskFactory().StartNew(() =&amp;gt;
            {
                &lt;span class="kwrd"&gt;new&lt;/span&gt; Task(() =&amp;gt; DoWork(125000), TaskCreationOptions.AttachedToParent).Start();
                &lt;span class="kwrd"&gt;new&lt;/span&gt; Task(() =&amp;gt; DoWork(125000), TaskCreationOptions.AttachedToParent).Start();
                &lt;span class="kwrd"&gt;new&lt;/span&gt; Task(() =&amp;gt; DoWork(125000), TaskCreationOptions.AttachedToParent).Start();
                &lt;span class="kwrd"&gt;new&lt;/span&gt; Task(() =&amp;gt; DoWork(125000), TaskCreationOptions.AttachedToParent).Start();
            });

            mainTask.ContinueWith((t) =&amp;gt;
            {
                watch.Stop();
                Console.WriteLine(watch.Elapsed);
            }, TaskContinuationOptions.OnlyOnRanToCompletion);

            Console.ReadLine();
        }

        &lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;static&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; DoWork(&lt;span class="kwrd"&gt;int&lt;/span&gt; rows)
        {
            &lt;span class="kwrd"&gt;string&lt;/span&gt; connStr = &lt;span class="str"&gt;&amp;quot;Data Source=.;Initial Catalog=DemoInserts&lt;br /&gt;&lt;/span&gt;&lt;span class="str"&gt;;Integrated Security=true;Application Name=TestInserts&amp;quot;&lt;/span&gt;;

            &lt;span class="kwrd"&gt;int&lt;/span&gt; countIterations = 0;
            &lt;span class="kwrd"&gt;while&lt;/span&gt; (countIterations &amp;lt; rows)
            {
                &lt;span class="kwrd"&gt;using&lt;/span&gt; (SqlConnection conn = &lt;span class="kwrd"&gt;new&lt;/span&gt; SqlConnection(connStr))
                {
                    conn.Open();

                    &lt;span class="kwrd"&gt;using&lt;/span&gt; (SqlCommand cmd = &lt;span class="kwrd"&gt;new&lt;/span&gt; SqlCommand())
                    {
                        cmd.Connection = conn;
                        cmd.CommandText = &lt;span class="str"&gt;&amp;quot;INSERT INTO Test(Id,Payload) VALUES (NEWID(), REPLICATE(&amp;#39;X&amp;#39;,20))&amp;quot;&lt;/span&gt;;
                        cmd.ExecuteNonQuery();
                        countIterations++;
                    }
                }
            }
        }
    }
}&lt;/pre&gt;


&lt;p&gt;Como podéis ver, el código simplemente instancia 4 tareas (una por cara núcleo disponible en mi máquina). Estas tareas reciben el número de filas a insertar, y proceden a realizar estas inserciones como comandos SQL dentro de un bucle. &lt;/p&gt;

&lt;p&gt;La inserción de las dichosas 500.000 filas por este mecanismo se produjo en &lt;strong&gt;1 minuto y 7 segundos&lt;/strong&gt;; si bien el valor no está nada mal en términos de mejora, sabía que aún me quedaba mucho para tener un tiempo competitivo. Una nueva visita al &lt;em&gt;Task Manager&lt;/em&gt; me hizo darme cuenta rápidamente de que, si bien había aumentado mucho mi consumo de CPU, aparentemente aún disponía de mucho margen adicional:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_4BF78D60.png"&gt;&lt;img style="background-image:none;border-bottom:0px;border-left:0px;padding-left:0px;padding-right:0px;display:block;float:none;margin-left:auto;border-top:0px;margin-right:auto;border-right:0px;padding-top:0px;" title="image" border="0" alt="image" src="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_thumb_5F00_3E44D158.png" width="444" height="323" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A la vista de esto, decidí probar a aumentar el número de hilos empleados para lanzar las sentencias INSERT, y monitorizar tras cada prueba tanto la duración de la misma como la utilización de los procesadores durante el transcurso de las inserciones. De este modo pude lograr grandes usos del procesador, pero es importante tener presente una cuestión:&lt;em&gt; el incremento de la actividad de CPU no implica necesariamente un mejor rendimiento en las inserciones. &lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Para demostrar este punto, os adjunto una pequeña gráfica que muestra los tiempos obtenidos en función del número de hilos empleados:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_3915A1BA.png"&gt;&lt;img style="background-image:none;border-bottom:0px;border-left:0px;padding-left:0px;padding-right:0px;display:block;float:none;margin-left:auto;border-top:0px;margin-right:auto;border-right:0px;padding-top:0px;" title="image" border="0" alt="image" src="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_thumb_5F00_0977832E.png" width="404" height="245" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Atiende que graficazaaa… ¿verdad que una de estas ayuda a darle un look más profesional a cualquier artículo? ;))&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Como se puede apreciar, el &lt;em&gt;sweet spot&lt;/em&gt; de este proceso de inserción en mi equipo se encuentra en los 8 hilos, con un tiempo de ejecución de &lt;strong&gt;43 segundos&lt;/strong&gt;. A continuación os muestro la gráfica de uso de CPU de esta ejecución, para que podáis contrastarla con la primera de todas y apreciéis el notable incremento en la utilización que hemos logrado:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_2F58783A.png"&gt;&lt;img style="background-image:none;border-bottom:0px;border-left:0px;padding-left:0px;padding-right:0px;display:block;float:none;margin-left:auto;border-top:0px;margin-right:auto;border-right:0px;padding-top:0px;" title="image" border="0" alt="image" src="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_thumb_5F00_18A60426.png" width="443" height="320" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;&amp;#160;&lt;/h3&gt;

&lt;h3&gt;¿Por donde seguimos ahora?&lt;/h3&gt;

&lt;p&gt;Bien, llegados a este punto ya tenía mas o menos claro el punto más idóneo de paralelismo desde el lado de la aplicación C# de inserciones, pero no conocía lo que estaba sucediendo por parte de SQL Server. Por esto, decidí estudiar las esperas del servidor; borre las estadísticas de esperas, lancé el proceso y a su finalización, use el celebérrimo script de &lt;a href="http://sqlserverperformance.wordpress.com/"&gt;Glenn Berry&lt;/a&gt;, personalizado por &lt;a href="http://www.sqlskills.com/BLOGS/PAUL/"&gt;Paul Randal&lt;/a&gt; para la &lt;a href="http://www.sqlskills.com/BLOGS/PAUL/post/Wait-statistics-or-please-tell-me-where-it-hurts.aspx"&gt;agregación de información de esperas por recursos en SQL Server&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;El resultado de la prueba, si bien era de esperar, resulta revelador:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_2C233DFA.png"&gt;&lt;img style="background-image:none;border-bottom:0px;border-left:0px;padding-left:0px;padding-right:0px;display:inline;border-top:0px;border-right:0px;padding-top:0px;" title="image" border="0" alt="image" src="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_thumb_5F00_06B94A8C.png" width="454" height="56" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Como se puede ver, un 99.22% del tiempo total de la prueba se destino a realizar entradas en el log de transacciones; esto se debe al hecho de que las inserciones, salvo casos muy puntuales en escenarios de BULK INSERT, son operaciones completamente logeadas. Dicho a lo bestia: por cada inserción, a parte del dato en si, hay que escribir la intención de realizar la inserción previamente en el log de transacciones. Esta operación es síncrona, y es la que esta provocando una gran cantidad de esperas.&lt;/p&gt;

&lt;p&gt;Para tratar de remediarlo, se me ocurrió tratar de reducir el número de cláusulas INSERT que enviamos al servidor mediante las sentencias de inserción múltiples de SQL Server 2008, que nos permiten insertar en el mismo statement hasta mil filas(*). Ya sabéis, algo así:&lt;/p&gt;

&lt;pre class="csharpcode"&gt;INSERT &lt;span class="kwrd"&gt;INTO&lt;/span&gt; Test(ID, Payload) &lt;span class="kwrd"&gt;VALUES&lt;/span&gt;
(NEWID(), &lt;span class="str"&gt;&amp;#39;AAA&amp;#39;&lt;/span&gt;),
(NEWID(), &lt;span class="str"&gt;&amp;#39;BBB&amp;#39;&lt;/span&gt;),
...
(NEWID(), &lt;span class="str"&gt;&amp;#39;XXX&amp;#39;&lt;/span&gt;)&lt;/pre&gt;


&lt;p&gt;Modifique un poco la aplicación para realizar estas sentencias, quedando el código del siguiente modo:&lt;/p&gt;

&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;using&lt;/span&gt; System;
&lt;span class="kwrd"&gt;using&lt;/span&gt; System.Collections.Generic;
&lt;span class="kwrd"&gt;using&lt;/span&gt; System.Linq;
&lt;span class="kwrd"&gt;using&lt;/span&gt; System.Text;
&lt;span class="kwrd"&gt;using&lt;/span&gt; System.Threading.Tasks;
&lt;span class="kwrd"&gt;using&lt;/span&gt; System.Data.SqlClient;
&lt;span class="kwrd"&gt;using&lt;/span&gt; System.Diagnostics;

&lt;span class="kwrd"&gt;namespace&lt;/span&gt; ConsoleApplication5
{
    &lt;span class="kwrd"&gt;class&lt;/span&gt; Program
    {

        &lt;span class="kwrd"&gt;static&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; Main(&lt;span class="kwrd"&gt;string&lt;/span&gt;[] args)
        {
            Stopwatch watch = Stopwatch.StartNew();

            var mainTask = &lt;span class="kwrd"&gt;new&lt;/span&gt; TaskFactory().StartNew(() =&amp;gt;
            {
                &lt;span class="kwrd"&gt;new&lt;/span&gt; Task(() =&amp;gt; DoWork(), TaskCreationOptions.AttachedToParent).Start();
                &lt;span class="kwrd"&gt;new&lt;/span&gt; Task(() =&amp;gt; DoWork(), TaskCreationOptions.AttachedToParent).Start();
                &lt;span class="kwrd"&gt;new&lt;/span&gt; Task(() =&amp;gt; DoWork(), TaskCreationOptions.AttachedToParent).Start();
                &lt;span class="kwrd"&gt;new&lt;/span&gt; Task(() =&amp;gt; DoWork(), TaskCreationOptions.AttachedToParent).Start();
                &lt;span class="kwrd"&gt;new&lt;/span&gt; Task(() =&amp;gt; DoWork(), TaskCreationOptions.AttachedToParent).Start();
                &lt;span class="kwrd"&gt;new&lt;/span&gt; Task(() =&amp;gt; DoWork(), TaskCreationOptions.AttachedToParent).Start();
                &lt;span class="kwrd"&gt;new&lt;/span&gt; Task(() =&amp;gt; DoWork(), TaskCreationOptions.AttachedToParent).Start();
                &lt;span class="kwrd"&gt;new&lt;/span&gt; Task(() =&amp;gt; DoWork(), TaskCreationOptions.AttachedToParent).Start();
            });

            mainTask.ContinueWith((t) =&amp;gt;
            {
                watch.Stop();
                Console.WriteLine(watch.Elapsed);
            }, TaskContinuationOptions.OnlyOnRanToCompletion);

            Console.ReadLine();
        }

        &lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;static&lt;/span&gt; &lt;span class="kwrd"&gt;void&lt;/span&gt; DoWork()
        {
            &lt;span class="kwrd"&gt;string&lt;/span&gt; connStr = &lt;span class="str"&gt;&amp;quot;Data Source=.;Initial Catalog=DemoInserts;Integrated Security=true;&lt;br /&gt;&lt;/span&gt;&lt;span class="str"&gt;Application Name=DemoInserts&amp;quot;&lt;/span&gt;;

            &lt;span class="kwrd"&gt;int&lt;/span&gt; countIterations = 0;
            &lt;span class="kwrd"&gt;while&lt;/span&gt; (countIterations &amp;lt; 63) &lt;span class="rem"&gt;// 63&lt;/span&gt;
            {
                &lt;span class="kwrd"&gt;using&lt;/span&gt; (SqlConnection conn = &lt;span class="kwrd"&gt;new&lt;/span&gt; SqlConnection(connStr))
                {
                    conn.Open();

                    &lt;span class="kwrd"&gt;using&lt;/span&gt; (SqlCommand cmd = &lt;span class="kwrd"&gt;new&lt;/span&gt; SqlCommand())
                    {
                        cmd.Connection = conn;

                        &lt;span class="kwrd"&gt;int&lt;/span&gt; countValues = 0;
                        var builder = &lt;span class="kwrd"&gt;new&lt;/span&gt; StringBuilder();

                        builder.Append(&lt;span class="str"&gt;&amp;quot;INSERT INTO Test(Id,Payload) VALUES &amp;quot;&lt;/span&gt;);

                        &lt;span class="kwrd"&gt;while&lt;/span&gt; (countValues &amp;lt; 1000)
                        {
                            builder.Append(&lt;span class="str"&gt;&amp;quot;(NEWID(), REPLICATE(&amp;#39;X&amp;#39;,20)) &amp;quot;&lt;/span&gt;);

                            &lt;span class="kwrd"&gt;if&lt;/span&gt; (countValues != 999)
                                builder.Append(&lt;span class="str"&gt;&amp;quot;,&amp;quot;&lt;/span&gt;);

                            countValues++;
                        }

                        cmd.CommandText = builder.ToString();
                        cmd.ExecuteNonQuery();
                        countIterations++;
                    }
                }
            }
        }
    }
}&lt;/pre&gt;

&lt;p&gt;Con esta nueva versión, usando ocho hilos, baje el tiempo a… (redoble de tambor, por favor)&lt;strong&gt; 4 segundos escasos!!!! &lt;/strong&gt;&lt;/p&gt;

&lt;p align="left"&gt;Llegados a este punto me sorprendí yo mismo; no creí que pudiera bajar tanto el tiempo de estas inserciones, y por primera vez vi peligrar mi dentadura ;) Como me encanta tener datos y números de cada mejora que voy logrando, volví a lanzar la prueba y consulté las estadísticas de esperas del servidor durante el tiempo de ejecución de la misma:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_16E51285.png"&gt;&lt;img style="background-image:none;border-bottom:0px;border-left:0px;padding-left:0px;padding-right:0px;display:inline;border-top:0px;border-right:0px;padding-top:0px;" title="image" border="0" alt="image" src="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_thumb_5F00_6E663070.png" width="454" height="64" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Como podéis ver, el hecho de agrupar las inserciones en batches ha hecho milagros! ahora mismo el servidor esta dedicando aproximadamente el mismo tiempo a escribir en el log de transacciones que en en resolver bloqueos de latches, una situación más normal, y que ha repercutido de manera espectacular en el rendimiento de estas inserciones.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(*): Por si alguien se lo pregunta, yo tampoco tengo ni puñetera idea de por qué el hard-limit de mil filas.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;Y por hoy ha estado bien, ¿no? ;)&lt;/h3&gt;

&lt;p&gt;Con esta entrada he buscado abriros un poco el apetito y, con un poco de suerte, picar a alguien más para que entre al pequeño juego que tenemos Unai y yo. He explicado mis aproximaciones iniciales, pero me reservo para la siguiente entrada la solución ‘buena, buena’ desde el lado del SQL Server.&lt;/p&gt;

&lt;p&gt;Para los mas ansiosos os iré adelantando que se basa en particionar la tabla para evitar contención sobre la misma; los detalles de implementación los pondré en mi siguiente post, pero como mis actualizaciones son como los despertares del &lt;strong&gt;lazy_writer&lt;/strong&gt; (&lt;em&gt;ya sabéis, puede ser dentro de 3 milisegundos, o dentro de 3 años!&lt;/em&gt;), os he querido adelantar la técnica por si queréis investigarla por vosotros mismos.&lt;/p&gt;

&lt;p&gt;A modo de resumen, voy a finalizar con otra gráfica de estas elegantes y vistosas, que hoy tengo el día ejecutivo ;)&lt;/p&gt;

&lt;p&gt;&lt;a href="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_650CC175.png"&gt;&lt;img style="background-image:none;border-bottom:0px;border-left:0px;padding-left:0px;padding-right:0px;display:block;float:none;margin-left:auto;border-top:0px;margin-right:auto;border-right:0px;padding-top:0px;" title="image" border="0" alt="image" src="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_thumb_5F00_6A7B3219.png" width="404" height="244" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;&lt;font style="font-weight:normal;"&gt;&lt;/font&gt;&lt;/h5&gt;

&lt;h5&gt;&lt;font style="font-weight:normal;"&gt;Por cierto, si os preguntáis que tal lo hizo MongoDb… vais a tener que esperar a que Unai lo publique en &lt;a href="http://geeks.ms/blogs/unai/"&gt;su blog&lt;/a&gt;. No obstante, os puedo adelantar que fue capaz de bajar de estos 4 segundos….&lt;/font&gt;&lt;/h5&gt;

&lt;p&gt;...&lt;/p&gt;

&lt;p&gt;..&lt;/p&gt;

&lt;p&gt;&lt;font style="font-weight:normal;"&gt;.&lt;/font&gt;&lt;/p&gt;

&lt;p&gt;&lt;font style="font-weight:normal;"&gt;&lt;em&gt;¡¡¡¡Pero yo también!!!! Muahahahaha!!! ;)&lt;/em&gt;&lt;/font&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Keep Rockin’!&lt;/em&gt;&lt;/p&gt;

&lt;h5&gt;Rock Tip:&lt;/h5&gt;

&lt;p&gt;Hacía mucho tiempo que no compartía con vosotros uno de mis rock tips… o mejor dicho, hacía mucho que no compartía nada con vosotros! Tengo el blog muy dejado, pero un servidor no da para más :(&lt;/p&gt;

&lt;p&gt;En esta ocasión me traigo a uno de mis descubrimientos del año, los geniales &lt;a href="http://en.wikipedia.org/wiki/Reckless_love"&gt;Reckless Love&lt;/a&gt;. Banda finlandesa liderada por el increíble &lt;em&gt;Olly Herman&lt;/em&gt;, quien fuera vocalista de &lt;a href="http://en.wikipedia.org/wiki/Crashd%C3%AFet"&gt;Crashdiet&lt;/a&gt; por una breve temporada y una de las personas con mas carisma y buen rollo que he visto nunca sobre un escenario. Herederos de las mejores raíces del hard rock melódico ochentero, pero con un increíble toque &lt;em&gt;dance&lt;/em&gt; y discotequero, puedo afirmar que verles en directo es la experiencia mas cercana a ver al &lt;a href="http://en.wikipedia.org/wiki/David_Lee_Roth"&gt;David Lee Roth&lt;/a&gt; sin recurrir a un De Lorean con condensador de fluzo.&lt;/p&gt;

&lt;p&gt;El tema que he escogido es el single de su segundo (y último hasta la fecha) disco: &lt;a href="http://www.youtube.com/watch?v=LV8wPokZNig"&gt;Hot&lt;/a&gt;. No tiene gran relación con la temática de la entrada, pero realmente me apetece compartir este tema con el mundo: si a uno solo de vosotros le hace sentir tan solo una decima parte de lo bien que me sienta a mi escuchar este temazo, ya habrá justificado saltarme mi costumbre de poner temas relacionados. &lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://geeks.ms/aggbug.aspx?PostID=201723" width="1" height="1"&gt;</description><category domain="http://geeks.ms/blogs/palvarez/archive/tags/SQL+Server/default.aspx">SQL Server</category><category domain="http://geeks.ms/blogs/palvarez/archive/tags/Rendimiento/default.aspx">Rendimiento</category><category domain="http://geeks.ms/blogs/palvarez/archive/tags/MongoDB/default.aspx">MongoDB</category></item><item><title>Tell Me Where To Go: Estudio de Consultas AdHoc en SQL Server</title><link>http://geeks.ms/blogs/palvarez/archive/2010/07/27/da-consultas-adhoc.aspx</link><pubDate>Tue, 27 Jul 2010 15:39:00 GMT</pubDate><guid isPermaLink="false">2a2e7ade-7474-448b-9de5-1515d8bb7d1b:179905</guid><dc:creator>Pablo Alvarez</dc:creator><slash:comments>6</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://geeks.ms/blogs/palvarez/rsscomments.aspx?PostID=179905</wfw:commentRss><comments>http://geeks.ms/blogs/palvarez/archive/2010/07/27/da-consultas-adhoc.aspx#comments</comments><description>&lt;p&gt;Uno de mis temas favoritos a la hora de dar charlas de rendimiento de SQL Server es el estudio de la cache de planes de ejecución y la presencia de consultas AdHoc. Puedo pasarme horas hablando sobre ello, y contando batallitas… muy a pesar de los pobres asistentes que deben acabar hasta las narices de las consultas AdHoc, de mí, y de mi santa madre :)&lt;/p&gt;  &lt;p&gt;Pero no nos engañemos, si me apasiona tanto este tema es porque se trata de un aspecto muy importante de la salud del servidor y de las aplicaciones que le atacan. Y, además, resulta muy fácil comprobar si estamos sufriendo algún problema relacionado con la presencia masiva de consultas AdHoc en el sistema. &lt;/p&gt;  &lt;p&gt;Lo curioso es que, a pesar de lo mucho que me gusta y lo importante que es el tema, no le he hecho justicia con artículos en este blog. Y con esto no quiero decir que no haya escrito acerca de ello; &lt;a href="http://geeks.ms/blogs/palvarez/archive/2008/05/18/these-days-un-d-237-a-en-la-vida-de-una-consulta.aspx"&gt;aquí&lt;/a&gt; podéis encontrar una breve descripción del ciclo de vida de las consultas en SQL Server, y &lt;a href="http://geeks.ms/blogs/palvarez/archive/2008/06/13/out-of-time-a-vueltas-con-los-planes-compilados-en-sql-server-2008.aspx"&gt;aquí&lt;/a&gt; podéis leer sobre la opción de optimización para cargas de trabajo AdHoc en SQL Server 2008. &lt;/p&gt;  &lt;p&gt;Sin embargo, creo que nunca he explicado como procedo a estudiar el estado de salud de la cache de planes de ejecución y procedimientos almacenados, así que espero redimirme un poquito con esta entrada.&lt;/p&gt;  &lt;p&gt;&lt;em&gt;NOTA: En esta entrada no voy a explicar el qué son las consultas AdHoc, ni el por qué pueden resultar perniciosas desde el punto de vista del rendimiento. Para esta explicación os recomiendo revisar los dos enlaces que os he puesto un poco más arriba.&lt;/em&gt; En esta ocasión solo me centraré en el proceso de estudio de estas consultas.&lt;/p&gt;  &lt;h4&gt;Vistazo General&lt;/h4&gt;  &lt;p&gt;Una de las maneras más rápidas y cómodas de comprobar si tenemos una elevada presencia de consultas AdHoc en el sistema es mediante los informes del SQL Server Management Studio (SSMS).&lt;/p&gt;  &lt;p&gt;Aunque parezca mentira, voy a dedicar un par de párrafos y capturas de pantalla a detallar como localizar estos informes. Con el paso de los años y las charlas es ido aprendiendo que, curiosamente, estos informes son tan potentes como desconocidos. Lo más habitual es que más de la mitad de los asistentes a mis sesiones desconozcan la existencia de los mismos, por lo que aprovecho para presentarlos a quienes aún no los conozcáis.&lt;/p&gt;  &lt;p&gt;Dentro de la interfaz del SSMS, y más concretamente, en el menú pop-up (el del botón derecho del ratón) de todos los nodos del Object Explorer, tenemos una opción llamada Reports.&lt;/p&gt;  &lt;p&gt;&lt;a href="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_7A4F165B.png"&gt;&lt;img style="border-right-width:0px;display:inline;border-top-width:0px;border-bottom-width:0px;margin-left:0px;border-left-width:0px;margin-right:0px;" title="image" border="0" alt="image" align="right" src="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_thumb_5F00_3D6A6EBC.png" width="137" height="244" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;Esta opción es contextual: vamos a tener informes a nivel de instancia, a nivel de base de datos, de usuarios, etc. Evidentemente, hay ciertos tipos de nodos que no tienen ningún informe predefinido, pero siempre podríamos crearnos informes personalizados incluso para los nodos vacíos; son simples ficheros RDLC.&lt;/p&gt;  &lt;p&gt;Si bien en esta caso solo vamos a trabajar con un informe concreto, os recomiendo encarecidamente que os deis una vuelta por los informes que vienen predefinidos (&lt;em&gt;Standard Reports&lt;/em&gt;), y especialmente por los definidos sobre el nodo de instancia y los definidos sobre los nodos de bases de datos específicas.&lt;/p&gt;  &lt;p&gt;En esta caso concreto, nos interesa un informe llamado &lt;strong&gt;Server Dashboard&lt;/strong&gt;, que se encuentra a nivel de instancia. Por tanto, para llegar a él, pinchamos con el botón derecho sobre el nodo de la instancia. Ahí vámos a &lt;em&gt;Reports –&amp;gt; Standard Reports –&amp;gt; Server Dashboard&lt;/em&gt;.&lt;/p&gt;  &lt;p&gt;&lt;i&gt;NOTA: Esto habituado a trabajar con la versión del producto en inglés, por lo que me vais a perdonar que los pantallazos estén en este idioma, y que no realice las traducciones pertinentes.&lt;/i&gt;&lt;/p&gt;  &lt;p&gt;&lt;a href="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_037B2BD0.png"&gt;&lt;img style="border-right-width:0px;display:inline;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;" title="image" border="0" alt="image" src="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_thumb_5F00_6C6CAA93.png" width="534" height="366" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;Como su propio nombre indica, este informe nos muestra un vistazo general del servidor, con la configuración del mismo, versiones del producto, detalles de actividad, etc. Y, cómo no, también nos deja ver un par de gráficas de tarta que alegran el informe con su vivos colores :P&lt;/p&gt;  &lt;p&gt;Estas gráficas muestran el porcentaje de utilización de CPU y de E/S por parte de cada una de las bases de datos del sistema, algo bastante útil para detectar si alguna de las bases de datos está monopolizando los recursos del sistema.&lt;/p&gt;  &lt;p&gt;A continuación os muestro, a modo de ejemplo, un pantallazo del informe en mi máquina local:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_4AA0D202.png"&gt;&lt;img style="border-right-width:0px;display:inline;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;" title="image" border="0" alt="image" src="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_thumb_5F00_1D3F3C32.png" width="534" height="463" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;Si os fijáis, las tareas que más CPU están empleando en mi entorno no son relacionadas con ninguna de las bases de datos presentes (&lt;em&gt;msdb&lt;/em&gt;, &lt;em&gt;PerfDW&lt;/em&gt;, &lt;em&gt;ReportServer&lt;/em&gt; y &lt;em&gt;db_ProcedimientosAdministrativos&lt;/em&gt;), sino con tareas AdHoc.&lt;/p&gt;  &lt;p&gt;Como podéis ver, un rápido vistazo a este informe nos muestra que podríamos estar experimentando un problema de consultas AdHoc en el sistema, ya que el consumo de CPU de las consultas AdHoc muy es elevado. ¿Quiere esto decir que si en la gráfica no aparece un gran consumo de CPU o E/S por consultas AdHoc, estamos libres del problema?&lt;/p&gt;  &lt;h4&gt;… pues va a ser que no :)&lt;/h4&gt;  &lt;p&gt;Cierto es… aunque tengamos muy poca presencia de consultas &lt;em&gt;AdHoc &lt;/em&gt;en el diagrama, es posible que las consultas de este tipo sean un problema en nuestro sistema. Desde aquí, haciendo un pequeño esfuerzo, puedo escuchar vuestras voces de incredulidad en la distancia:&lt;/p&gt;  &lt;p&gt;- ¡¿Cómo es posible?! &lt;em&gt;(…se oyen las voces desde más alla del Manzanares…)      &lt;br /&gt;&lt;/em&gt;- Das ist Unglaublich!! &lt;em&gt;(…algun avezado lector desde la puerta de Brandemburgo…)&lt;/em&gt;     &lt;br /&gt;- Cómo ye, ¡ho! &lt;em&gt;(…estremecedores palabras que oigo entre Mieres y La Moreda…)&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;¡Muy fácil! Puede ser que el sistema tenga miles de consultas AdHoc, muy livianas, cuya ejecución no es costosa ni en CPU ni en E/S, pero sin embargo genera presión por recompilación, etc.&lt;/p&gt;  &lt;h4&gt;Entrando en Detalle&lt;/h4&gt;  &lt;p&gt;Vale, ya sabemos que puede ser necesario investigar un poco más ¿Cómo podemos hacerlo? Yo recomiendo empezar por comparar el ratio entre consultas AdHoc y consultas No AdHoc. A continuación os pongo un ejemplo capturado de un cliente en el que he estado recientemente: &lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;SELECT      &lt;br /&gt;&amp;#160;&amp;#160; COUNT(*)       &lt;br /&gt;FROM sys.dm_exec_cached_plans       &lt;br /&gt;WHERE objtype LIKE &amp;#39;Adhoc&amp;#39; &lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;En el caso concreto de este servidor, esta consulta ha devuelto &lt;strong&gt;15636&lt;/strong&gt; &lt;strong&gt;planes adhoc&lt;/strong&gt;. ¡No es precisamente un número pequeño de planes de ejecución en la cache! Vamos a ver ahora cuantos planes de ejecución no adhoc hay en la cache: &lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;SELECT      &lt;br /&gt;&amp;#160;&amp;#160; COUNT(*)       &lt;br /&gt;FROM sys.dm_exec_cached_plans       &lt;br /&gt;WHERE objtype NOT LIKE &amp;#39;Adhoc&amp;#39;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;La consulta anterior nos indica que hay &lt;strong&gt;706 planes de ejecución de consultas no AdHoc&lt;/strong&gt;. Evidentemente, el ratio entre unas y otras es extremadamente preocupante, y signo evidente de que el sistema está generando constantemente consultas AdHoc. &lt;/p&gt;  &lt;p&gt;&lt;em&gt;NOTA: Es difícil dar reglas a seguir en estos casos, pero generalmente puedo decir que si detecto que más del 30% de las consultas de la cache son de tipo AdHoc, considero que hay un problema que investigar respecto a la generación y parametrización de las consultas.&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;El siguiente paso sería detectar la memoria empleada en planes reutilizados, y en planes no reutilizados, y catalogarlos apropiadamente. En este caso yo utilizo un script, basado en el excelente trabajo de &lt;a href="http://sqlblog.com/blogs/davide_mauri/archive/2010/07/23/viewing-how-much-memory-is-used-by-not-reused-query-plan.aspx"&gt;Davide Mauri&lt;/a&gt;, que me viene de perlas en estos casos:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;WITH Planes AS      &lt;br /&gt;(       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; SELECT       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Reutilizado = CASE       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; WHEN usecounts &amp;gt; 1 THEN &amp;#39;Plan Reutilizado (Mb)&amp;#39;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ELSE &amp;#39;Plan No Reutilizado (Mb)&amp;#39;       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; END,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; size_in_bytes AS [Tamaño (Mb)],       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; cacheobjtype AS TipoObjetoCache,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; objtype AS Tipo       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; FROM       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; sys.dm_exec_cached_plans       &lt;br /&gt;),       &lt;br /&gt;Agregado AS       &lt;br /&gt;(       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; SELECT       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Reutilizado,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Tipo,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; TipoObjetoCache,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; [Tamaño (Mb)] = SUM([Tamaño (Mb)] / 1024. / 1024.)       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; FROM       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Planes       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; GROUP BY       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Reutilizado,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; TipoObjetoCache,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Tipo       &lt;br /&gt;),       &lt;br /&gt;Pivote AS       &lt;br /&gt;(       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; SELECT *       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; FROM       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Agregado a       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; PIVOT       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ( SUM([Tamaño (Mb)]) FOR Reutilizado IN ([Plan Reutilizado (Mb)], [Plan No Reutilizado (Mb)]) ) p       &lt;br /&gt;)       &lt;br /&gt;SELECT       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; Tipo,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; TipoObjetoCache,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; [Tamaño Reutilizado (Mb)] = SUM([Plan Reutilizado (Mb)]),       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; [Tamaño No Reutilizado (Mb)] = SUM([Plan No Reutilizado (Mb)])       &lt;br /&gt;FROM       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; Pivote       &lt;br /&gt;GROUP BY       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; Tipo,       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; TipoObjetoCache       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; WITH ROLLUP       &lt;br /&gt;HAVING       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; (Tipo IS NULL AND TipoObjetoCache IS NULL ) OR       &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; (Tipo IS NOT NULL AND TipoObjetoCache IS NOT NULL )&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Volviendo al caso de mi cliente, la ejecución de esta consulta devolvió los siguientes resultados:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_32497988.png"&gt;&lt;img style="border-right-width:0px;display:inline;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;" title="image" border="0" alt="image" src="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_thumb_5F00_09CA9774.png" width="534" height="229" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;Como podéis comprobar, en este caso el tamaño no reutilizado es casi similar al listado para los planes reutilizados, lo cual es otro indicativo claro de que hay un gran problema de no reutilización de planes de ejecución debido a consultas AdHoc mal parametrizadas. &lt;/p&gt;  &lt;p&gt;Después de comprobar que realmente estamos sufriendo el problema ¿cual sería mi siguiente paso? Pues el más divertido de todos… &lt;em&gt;¡buscar al culpable!&lt;/em&gt; ;)&lt;/p&gt;  &lt;p&gt;Para ello, buscaría las consultas que aparecen en la cache, de tipo AdHoc, y cruzaría la vista con la funcion &lt;em&gt;sys.dm_exec_sql_text&lt;/em&gt; para poder sacar el texto de la SQL a partir del handle:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;SELECT      &lt;br /&gt;&amp;#160;&amp;#160; refcounts,       &lt;br /&gt;&amp;#160;&amp;#160; size_in_bytes,       &lt;br /&gt;&amp;#160;&amp;#160; cacheobjtype,       &lt;br /&gt;&amp;#160;&amp;#160; text       &lt;br /&gt;FROM       &lt;br /&gt;&amp;#160;&amp;#160; sys.dm_exec_cached_plans       &lt;br /&gt;&amp;#160;&amp;#160; CROSS APPLY sys.dm_exec_sql_text(plan_handle)       &lt;br /&gt;WHERE objtype LIKE &amp;#39;Adhoc&amp;#39;&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Os muestro una salida parcial para que veais el problema (sin mostrar toda la consulta y revelar parte del modelo de datos de mi cliente, evidentmente):&lt;/p&gt;  &lt;p&gt;&lt;a href="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_74CBB961.png"&gt;&lt;img style="border-right-width:0px;display:inline;border-top-width:0px;border-bottom-width:0px;border-left-width:0px;" title="image" border="0" alt="image" src="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/image_5F00_thumb_5F00_6DCC06DC.png" width="534" height="212" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;Como se puede ver, las consultas son iguales, variando exclusivamente en los parámetros; el hecho de que sigan recompilandose y no se reutilicen es significativo, y un potencial problema de rendimiento (como ya se comentó en los otros posts). &lt;/p&gt;  &lt;p&gt;¡Ya tenemos nuestros culpables! ¡¡Yeah!!&lt;/p&gt;  &lt;h4&gt;Resumiendo&lt;/h4&gt;  &lt;p&gt;A modo de resumen, yo abordaría el estudio del siguiente modo:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;Revisión del informe&lt;em&gt; &lt;strong&gt;Server Dashboard&lt;/strong&gt;&lt;/em&gt; para buscar presencia de consultas &lt;em&gt;AdHoc&lt;/em&gt; &lt;/li&gt;    &lt;li&gt;Consulta sobre &lt;em&gt;&lt;strong&gt;sys.dm_exec_cached_plans&lt;/strong&gt;&lt;/em&gt; para ver el ratio &lt;em&gt;AdHoc/No AdHoc&lt;/em&gt; &lt;/li&gt;    &lt;li&gt;Consulta para ver la memoria fugada en planes no reutilizados &lt;/li&gt;    &lt;li&gt;Si procede, revisar las consultas problematicas. &lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;Fácil, sencillo y para toda la familia :) &lt;/p&gt;  &lt;p&gt;Después de ver esta información, tendremos una buena idea de si estamos siendo víctimas de algún tipo de problema derivado de la presencia masiva de consultas &lt;em&gt;AdHoc&lt;/em&gt; en nuestro sistema. Si fuera así, habrá que comprobar la presencia de consultas dinámicas sin preparar, consultas sin parametrizar desde el cliente, etc.&lt;/p&gt;  &lt;p&gt;Espero que os haya resultado interesante la entrada, y os animo a que probéis en vuestros servidores SQL Server: ¡seguro que más de uno de vosotros se lleva una sorpresa!&lt;/p&gt;  &lt;p&gt;&lt;b&gt;Rock Tip:&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;Los últimos rock tips, si bien han sido temazos, se han alejado un poco de mi corazoncito hardrockero ochentero, así que es buen momento de retomarlo. Y para ocasiones especiales, nada mejor que los grandísimos &lt;i&gt;glam-metaleros&lt;/i&gt; noruegos &lt;a href="http://en.wikipedia.org/wiki/Wig_Wam"&gt;Wig Wam&lt;/a&gt;.&lt;/p&gt;  &lt;p&gt;Representantes de su país en el festival de Eurovisión de 2005, con el temazo ‘&lt;a href="http://www.youtube.com/watch?v=0XIyCKgA8z8"&gt;In My Dreams&lt;/a&gt;’, en esta ocasión nos quedamos con su balada ‘&lt;a href="http://www.youtube.com/watch?v=f-csCNH5Ds8"&gt;Tell Me Where To Go&lt;/a&gt;’ para acompañar este post. &lt;/p&gt;  &lt;p&gt;A todo esto, apuntaros esta fecha en vuestros calendarios: 18 de Septiembre de 2010. Ese será el día que los chicos de WigWam nos visitarán en Madrid por primera vez, en la sala Ritmo y Compás ¡No creí que mis ojos fueran a ver esto! Ya solo queda ver por estas tierras a &lt;a href="http://en.wikipedia.org/wiki/Steel_Panther"&gt;Steel Panther&lt;/a&gt; y seré un hombre plenamente feliz :)&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://geeks.ms/aggbug.aspx?PostID=179905" width="1" height="1"&gt;</description><category domain="http://geeks.ms/blogs/palvarez/archive/tags/SQL+Server/default.aspx">SQL Server</category><category domain="http://geeks.ms/blogs/palvarez/archive/tags/Rendimiento/default.aspx">Rendimiento</category></item><item><title>Pinball Map: Cuidado con los Mapeos en NHibernate</title><link>http://geeks.ms/blogs/palvarez/archive/2010/06/08/pinball-map-cuidado-con-los-mapeos-en-nhibernate.aspx</link><pubDate>Tue, 08 Jun 2010 11:25:00 GMT</pubDate><guid isPermaLink="false">2a2e7ade-7474-448b-9de5-1515d8bb7d1b:177786</guid><dc:creator>Pablo Alvarez</dc:creator><slash:comments>31</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://geeks.ms/blogs/palvarez/rsscomments.aspx?PostID=177786</wfw:commentRss><comments>http://geeks.ms/blogs/palvarez/archive/2010/06/08/pinball-map-cuidado-con-los-mapeos-en-nhibernate.aspx#comments</comments><description>&lt;p&gt;Ermm… si, chicos y chicas, sonará raro, pero hoy voy a hablar de NHibernate. Sé que me arriesgo a la condenación eterna en las llamas del infierno, pero yo soy así de altruista y me la juego por vosotros! ;)&lt;/p&gt;  &lt;p&gt;Uno de los clientes con los que he estado esta semana(*) estaba experimentando problemas de rendimiento en el acceso a datos en su aplicación, sospechando que el causante pudiera ser una vista poco optimizada. Afortunadamente, el cliente tenía muy bien localizada la operativa más costosa, hasta el punto que podíamos llegar con gran facilidad a la consulta problemática, que era la siguiente:&lt;/p&gt;  &lt;p&gt;&lt;font size="1" face="Consolas"&gt;EXEC sp_executesql N&amp;#39;     &lt;br /&gt;SELECT       &lt;br /&gt;&amp;#160;&amp;#160; this_.ID as ID90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.BorradoLogico as BorradoL2_90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.Descripcion as Descripc3_90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.Estado as Estado90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.VisibleWeb as VisibleWeb90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.CodigoDB2 as CodigoDB6_90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.FechaPrevistaInicioComercializacion as FechaPre7_90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.FechaFinComercializacion as FechaFin8_90_0_,&amp;#160; &lt;br /&gt;&amp;#160;&amp;#160; this_.Comentarios as Comentar9_90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.URLFoto as URLFoto90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.Orden as Orden90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.URLFotoMiniatura as URLFoto12_90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.SufijoID as SufijoID90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.MarcaID as MarcaID90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.VersionId as VersionId90_0_       &lt;br /&gt;FROM&amp;#160; &lt;br /&gt;&amp;#160;&amp;#160; VistaProblematica this_       &lt;br /&gt;WHERE       &lt;br /&gt;&amp;#160;&amp;#160; this_.CodigoDB2 in (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8, @p9) and       &lt;br /&gt;&amp;#160;&amp;#160; this_.SufijoID in (@p10, @p11, @p12, @p13, @p14) and this_.BorradoLogico = @p15&amp;#39;,      &lt;br /&gt;N&amp;#39;@p0 &lt;font color="#ff0000"&gt;nvarchar(5)&lt;/font&gt;,@p1 &lt;font color="#ff0000"&gt;nvarchar(5)&lt;/font&gt;,@p2 &lt;font color="#ff0000"&gt;nvarchar(5)&lt;/font&gt;,@p3 &lt;font color="#ff0000"&gt;nvarchar(5)&lt;/font&gt;,@p4 &lt;font color="#ff0000"&gt;nvarchar(5)&lt;/font&gt;,@p5 &lt;font color="#ff0000"&gt;nvarchar(5)&lt;/font&gt;,@p6 &lt;font color="#ff0000"&gt;nvarchar(5)&lt;/font&gt;,@p7 &lt;font color="#ff0000"&gt;nvarchar(5)&lt;/font&gt;,@p8 &lt;font color="#ff0000"&gt;nvarchar(5)&lt;/font&gt;,@p9 &lt;font color="#ff0000"&gt;nvarchar(5)&lt;/font&gt;,@p10 bigint,@p11 bigint,@p12 bigint,@p13 bigint,@p14 bigint,@p15 bit&amp;#39;,      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; @p0 = N&amp;#39;040&amp;#160; &amp;#39;, @p1 = N&amp;#39;1F7&amp;#160; &amp;#39;, @p2 = N&amp;#39;1G3&amp;#160; &amp;#39;, @p3 = N&amp;#39;209&amp;#160; &amp;#39;,      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; @p4 = N&amp;#39;3R3&amp;#160; &amp;#39;, @p5 = N&amp;#39;8S1&amp;#160; &amp;#39;, @p6 = N&amp;#39;8S6&amp;#160; &amp;#39;, @p7 = N&amp;#39;8T4&amp;#160; &amp;#39;,      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; @p8 = N&amp;#39;8T8&amp;#160; &amp;#39;, @p9 = N&amp;#39;9AK&amp;#160; &amp;#39;, @p10 = 1819, @p11 = 1820, @p12 = 1821,      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; @p13 = 1822, @p14 = 3614, @p15 = 0&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;De un primer vistazo, podemos ver que la consulta esta generada automáticamente por NHibernate. No es que sea inherentemente malo, solo lo digo a título informativo :)&lt;/p&gt;  &lt;p&gt;Procedimos a comprobar los tiempos de esta consulta con SET STATISTICS TIME ON:&lt;/p&gt;  &lt;p&gt;&lt;font size="1" face="cons"&gt;SQL Server Execution Times:     &lt;br /&gt;&amp;#160;&amp;#160; CPU time = &lt;font color="#ff0000"&gt;703 ms&lt;/font&gt;,&amp;#160; elapsed time = &lt;font color="#ff0000"&gt;796 ms&lt;/font&gt;&lt;/font&gt;.&lt;/p&gt;  &lt;p&gt;A la vista del tamaño de filas y la vista, sí que es cierto que el tiempo de ejecución de casi un segundo parece ser demasiado elevado. Por ello, procedimos a analizar el número de operaciones de E/S de cada elemento involucrado en la consulta, mediante SET STATISTICS IO ON:&lt;/p&gt;  &lt;p&gt;&lt;font size="1" face="consoles"&gt;(50 row(s) affected)     &lt;br /&gt;&lt;font color="#ff0000"&gt;Table &amp;#39;Worktable&amp;#39;. Scan count 51, logical reads 92037, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.&lt;/font&gt;      &lt;br /&gt;Table &amp;#39;Versiones&amp;#39;. Scan count 53, logical reads 316, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.      &lt;br /&gt;Table &amp;#39;Modelos&amp;#39;. Scan count 106, logical reads 424, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.      &lt;br /&gt;Table &amp;#39;Sufijos&amp;#39;. Scan count 9852, logical reads 20996, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.      &lt;br /&gt;Table &amp;#39;Colores&amp;#39;. Scan count 10, logical reads 292, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;Como se puede ver, hay bastante actividad de E/S en casi todas las tablas, pero lo que me llamó la atención por encima de todo fue la elevada actividad en las &lt;em&gt;worktables&lt;/em&gt;…&lt;/p&gt;  &lt;h5&gt;¿¡Worktables?! ¡¿Que mi madre es eso?!&lt;/h5&gt;  &lt;p&gt;Pues las worktables son un concepto muy importante en SQL Server, y a la vez, muy poco conocido. En muchas ocasiones son la causa de la degradación de rendimiento de un procedimiento almacenado al suministrarle diferentes parámetros, el origen de los temidos Sort Warning y Hash Warning, y la raíz de todos los males de la huma… perdón, que me dejé llevar :)&lt;/p&gt;  &lt;p&gt;Las worktables en realidad son un mal necesario; no son más que unas tablas temporales que se crean en tempDb para las operaciones de Sort y Hash que no han conseguido suficiente memoria como para hacerlas sobre el Buffer Pool, u operaciones grandes de cursores o tipos de datos LOB… en definitiva, son una solución de emergencia para cuando deseamos realizar una operación que va a requerir gran cantidad de memoria.&lt;/p&gt;  &lt;p&gt;Habría muchos ejemplos de su utilización, pero os voy a poner uno muy sencillo: Imaginad que tenemos un procedimiento almacenado para realizar un proceso que hace una JOIN de las filas de dos tablas en un periodo de tiempo determinado. Este procedimiento recibe dos parámetros: fecha de inicio y fecha de fin. &lt;/p&gt;  &lt;p&gt;Ahora imaginad que la primera vez que se despliega ese procedimiento, se ejecuta para un rango de un solo día. Ese procedimiento va a compilarse tratando de optimizar la ejecución para ese rango de fechas: SQL Server podría decidir realizar la JOIN mediante un operador HASH JOIN debido a que el número de filas no es demasiado elevado, y reservará la cantidad de memoria justa para realizar la operacion de HASH para esas filas.&lt;/p&gt;  &lt;p&gt;Supongamos que al día siguiente, esta consulta se realiza para un rango temporal de un mes entero; SQL Server ya tiene el plan cacheado, por lo que va a tratar de reutilizarlo, pero la asignación de memoria para la operación de HASH era la justa para las filas de un día. Al comenzar a ejecutar la consulta, el procesador de consultas detectará que no tiene memoria suficiente y decidirá terminar la operación sobre una Worktable en TempDb. Esto es mucho más lento, pero al menos garantiza que la operación pueda finalizar.&lt;/p&gt;  &lt;h5&gt;Volviendo al Turrón:&lt;/h5&gt;  &lt;p&gt;A la vista de la actividad de E/S en Worktables, decidí confirmar mis sospechas, por lo que adjunté una traza de profiler:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/profiler_5F00_70E247EA.png"&gt;&lt;img style="border-bottom:0px;border-left:0px;display:block;float:none;margin-left:auto;border-top:0px;margin-right:auto;border-right:0px;" title="profiler" border="0" alt="profiler" src="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/profiler_5F00_thumb_5F00_75948A3F.png" width="540" height="282" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;¡Bingo! Podemos comprobar que aparece un evento de &lt;em&gt;Hash Warning&lt;/em&gt; al ejecutar la consulta. &lt;/p&gt;  &lt;p&gt;Bien, ya sabemos la razón de nuestro problema de rendimiento: el uso de tempDb. Ahora solo nos queda saber por qué esta consulta esta ejecutándose en TempDb. &lt;/p&gt;  &lt;p&gt;Después de un par de ideas no muy afortunadas basadas en la recompilación de esa consulta, decidí estudiar los tipos de datos de la definición de esa tabla, para ver si coincidían con los que se pasaban como parámetros en el &lt;em&gt;sp_executesql. &lt;/em&gt;&lt;/p&gt;  &lt;p&gt;A continuación os muestro las columnas de la vista en cuestion. Permitidme que antes os avise de que lo que vais a ver a continuación puede herir la sensibilidad de más de un desarrollador o arquitecto de datos(**) :)&lt;/p&gt;  &lt;p&gt;&lt;a href="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/Definicion_5F00_1677D71A.png"&gt;&lt;img style="border-bottom:0px;border-left:0px;display:block;float:none;margin-left:auto;border-top:0px;margin-right:auto;border-right:0px;" title="Definicion" border="0" alt="Definicion" src="http://geeks.ms/cfs-file.ashx/__key/CommunityServer.Blogs.Components.WeblogFiles/palvarez/Definicion_5F00_thumb_5F00_7BCB3B00.png" width="373" height="321" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;Como podéis ver, la columna CodigoDB2 está definida como &lt;font color="#ff0000"&gt;char(5)&lt;/font&gt;, pero en el código de la consulta que genera NHibernate nos pasa el parámetro como &lt;font color="#ff0000"&gt;nvarchar(5)&lt;/font&gt;, lo cual aumenta el peso de la consulta considerablemente, y puede estar provocando que la memoria reservada para la consulta no sea suficiente y tenga que volcar la operación sobre worktables en tempDb.&lt;/p&gt;  &lt;p&gt;Hicimos la prueba, cambiando los tipos de datos sobre la consulta generada por NHibernate en el &lt;em&gt;sp_executesql&lt;/em&gt;, quedando la misma así:&lt;/p&gt;  &lt;p&gt;&lt;font size="1" face="Consolas"&gt;EXEC sp_executesql N&amp;#39;     &lt;br /&gt;SELECT       &lt;br /&gt;&amp;#160;&amp;#160; this_.ID as ID90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.BorradoLogico as BorradoL2_90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.Descripcion as Descripc3_90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.Estado as Estado90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.VisibleWeb as VisibleWeb90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.CodigoDB2 as CodigoDB6_90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.FechaPrevistaInicioComercializacion as FechaPre7_90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.FechaFinComercializacion as FechaFin8_90_0_,&amp;#160; &lt;br /&gt;&amp;#160;&amp;#160; this_.Comentarios as Comentar9_90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.URLFoto as URLFoto90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.Orden as Orden90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.URLFotoMiniatura as URLFoto12_90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.SufijoID as SufijoID90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.MarcaID as MarcaID90_0_,       &lt;br /&gt;&amp;#160;&amp;#160; this_.VersionId as VersionId90_0_       &lt;br /&gt;FROM       &lt;br /&gt;&amp;#160;&amp;#160; VistaProblematica this_       &lt;br /&gt;WHERE       &lt;br /&gt;&amp;#160;&amp;#160; this_.CodigoDB2 in (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8, @p9) and       &lt;br /&gt;&amp;#160;&amp;#160; this_.SufijoID in (@p10, @p11, @p12, @p13, @p14) and this_.BorradoLogico = @p15&amp;#39;,      &lt;br /&gt;N&amp;#39;@p0 &lt;font color="#ff0000"&gt;char(5)&lt;/font&gt;,@p1 &lt;font color="#ff0000"&gt;char(5)&lt;/font&gt;,@p2 &lt;font color="#ff0000"&gt;char(5)&lt;/font&gt;,@p3 &lt;font color="#ff0000"&gt;char(5)&lt;/font&gt;,@p4 &lt;font color="#ff0000"&gt;char(5)&lt;/font&gt;,@p5 &lt;font color="#ff0000"&gt;char(5)&lt;/font&gt;,@p6 &lt;font color="#ff0000"&gt;char(5)&lt;/font&gt;,@p7 &lt;font color="#ff0000"&gt;char(5)&lt;/font&gt;,@p8 &lt;font color="#ff0000"&gt;char(5)&lt;/font&gt;,@p9 &lt;font color="#ff0000"&gt;char(5)&lt;/font&gt;,@p10 bigint,@p11 bigint,@p12 bigint,@p13 bigint,@p14 bigint,@p15 bit&amp;#39;,      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; @p0 = N&amp;#39;040&amp;#160; &amp;#39;, @p1 = N&amp;#39;1F7&amp;#160; &amp;#39;, @p2 = N&amp;#39;1G3&amp;#160; &amp;#39;, @p3 = N&amp;#39;209&amp;#160; &amp;#39;,      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; @p4 = N&amp;#39;3R3&amp;#160; &amp;#39;, @p5 = N&amp;#39;8S1&amp;#160; &amp;#39;, @p6 = N&amp;#39;8S6&amp;#160; &amp;#39;, @p7 = N&amp;#39;8T4&amp;#160; &amp;#39;,      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; @p8 = N&amp;#39;8T8&amp;#160; &amp;#39;, @p9 = N&amp;#39;9AK&amp;#160; &amp;#39;, @p10 = 1819, @p11 = 1820, @p12 = 1821,      &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; @p13 = 1822, @p14 = 3614, @p15 = 0&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;Tras realizar los cambios, volví a ejecutar la consulta con la información de tiempos y actividad de E/S, y obtuve los siguientes resultados:&lt;/p&gt;  &lt;p&gt;&lt;font size="1" face="Consolas"&gt;SQL Server Execution Times:     &lt;br /&gt;&amp;#160;&amp;#160; CPU time = &lt;font color="#ff0000"&gt;78 ms&lt;/font&gt;,&amp;#160; elapsed time = &lt;font color="#ff0000"&gt;78 ms&lt;/font&gt;.&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;&lt;font size="1" face="Consolas"&gt;(50 row(s) affected)     &lt;br /&gt;&lt;font color="#ff0000"&gt;Table &amp;#39;Worktable&amp;#39;. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.       &lt;br /&gt;&lt;/font&gt;Table &amp;#39;Colores&amp;#39;. Scan count 10, logical reads 292, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.      &lt;br /&gt;Table &amp;#39;Sufijos&amp;#39;. Scan count 2, logical reads 27, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.      &lt;br /&gt;Table &amp;#39;Versiones&amp;#39;. Scan count 4, logical reads 22, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.      &lt;br /&gt;Table &amp;#39;Modelos&amp;#39;. Scan count 8, logical reads 32, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;¡No está nada mal! Hemos conseguido que la consulta se ejecute diez veces más rápido, sin hacer ninguna modificación real sobre la misma. Toda esa pérdida de tiempo se estaba produciendo por el uso de tempdb como consecuencia de la diferencia entre la memoria estimada y la necesaria.&lt;/p&gt;  &lt;p&gt;Al final, el cliente arregló el mapeo del tipo de datos, estableciendo el sql-type de modo explícito, lo que eliminó este problema puntual. La gran noticia es que, con toda seguridad, este cambio en el mapeo afectará a muchas otras consultas, por lo que se espera un incremento generalizado del rendimiento en todas las consultas.&lt;/p&gt;  &lt;h5&gt;Conclusiones:&lt;/h5&gt;  &lt;ul&gt;   &lt;li&gt;Es muy importante garantizar la corrección de los tipos de datos en los mapeos de cualquier solución de persistencia.&lt;/li&gt;    &lt;li&gt;Revisad el uso de vuestras tempdb, la creación de worktables y la presencia de eventos de tipo Hash Warning o Sort Warning en vuestros sistemas; es muy posible que os llevéis alguna sorpresa!&lt;/li&gt;    &lt;li&gt;Estaba claro que si yo iba a hablar acerca de NHibernate, no iba a ser enteramente para bien… ;)&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Keep Rockin’!&lt;/p&gt; &lt;em&gt;(*): Soy perfectamente consciente de lo mal que puede llegar a sonar esa frase!    &lt;br /&gt;(**): Tengo que dejarlo claro: el modelo de datos NO es mío!!! :)&lt;/em&gt;  &lt;h5&gt;Rock Tip:&lt;/h5&gt;  &lt;p&gt;En esta ocasión, voy a dar un poquito más de caña de lo habitual. Los que seguís este blog y sus Rock Tips(tm) sabéis que casi siempre me decanto por un puntito hardrockero melódico, que es lo que suelo escuchar gran parte del tiempo. Sin embargo, en esta ocasión creo que os voy a sorprender con uno de mis grupos favoritos, aunque son de un palo bastante mas heavy: se trata de los suecos ‘&lt;a href="http://en.wikipedia.org/wiki/In_Flames"&gt;In Flames&lt;/a&gt;’, grupo fundado y liderado hasta hace poco por ese genio que es Jesper Strömblad.&lt;/p&gt;  &lt;p&gt;Cuando estaba escribiendo este post sobre el mapeo en Hibernate, no pude evitar que se me viniera a la cabeza su tema ‘&lt;a href="http://www.youtube.com/watch?v=wCvq8VN9owY"&gt;Pinball Map&lt;/a&gt;’; quizá fue simplemente por la palabra map, o quizá porque necesitaba un poco de buen death metal melódico sueco para desintoxicarme de NHibernate xD Sea como sea, me apetece compartirlo con vosotros, así que.. enjoy! ;)&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://geeks.ms/aggbug.aspx?PostID=177786" width="1" height="1"&gt;</description><category domain="http://geeks.ms/blogs/palvarez/archive/tags/SQL+Server/default.aspx">SQL Server</category><category domain="http://geeks.ms/blogs/palvarez/archive/tags/Rendimiento/default.aspx">Rendimiento</category></item><item><title>Rainbow In The Dark: Materiales de la sesión de Optimización de SQL Server</title><link>http://geeks.ms/blogs/palvarez/archive/2010/06/01/rainbow-in-the-dark-materiales-de-la-sesi-243-n-de-optimizaci-243-n-de-sql-server.aspx</link><pubDate>Tue, 01 Jun 2010 18:13:00 GMT</pubDate><guid isPermaLink="false">2a2e7ade-7474-448b-9de5-1515d8bb7d1b:177557</guid><dc:creator>Pablo Alvarez</dc:creator><slash:comments>0</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://geeks.ms/blogs/palvarez/rsscomments.aspx?PostID=177557</wfw:commentRss><comments>http://geeks.ms/blogs/palvarez/archive/2010/06/01/rainbow-in-the-dark-materiales-de-la-sesi-243-n-de-optimizaci-243-n-de-sql-server.aspx#comments</comments><description>&lt;p&gt;El pasado Jueves 27 tuve la oportunidad de pasar un par de horas muy agradables en compañía de la gente de MAD.NUG, hablando de optimización de SQL Server. En resumen, vine a explicar un poco el proceso que yo sigo a la hora de enfrentarme a un problema de rendimiento en un entorno SQL Server que, en la práctica totalidad de los casos, me resulta desconocido.&lt;/p&gt;  &lt;p&gt;Empezamos repasando algunos de los informes básicos que el producto pone a nuestra disposición, para hacernos una idea de la salud general del servidor, y fuimos bajando de nivel y obteniendo más detalle gracias a la utilización de alguna de las muchas DMVs del sistema. Repasamos y explicamos las esperas del servidor (&lt;em&gt;sys.dm_os_wait_stats&lt;/em&gt;) y aproveché para contar un par de batallitas sobre optimizaciones extremas mediante la eliminación de CPUs :)&lt;/p&gt;  &lt;p&gt;Por último, dedicamos la última parte de la sesión a estudiar algunos escenarios curiosos de rendimiento, como el uso inapropiado de UDFs en los predicados de las consultas, el tan conocido empleo de cursores, los peligros de las consultas Ad Hoc y por último, un escenario muy interesante de ordenación en tempDb por problemas de estimación de memoria y &lt;em&gt;parameter sniffing&lt;/em&gt;.&lt;/p&gt;  &lt;p&gt;He de admitir que, para ser una sesión de SQL Server, me lo pasé bien! y es que no solo de WinDbg vive el hombre ;) &lt;/p&gt;  &lt;p&gt;Aprovecho para dejaros &lt;a href="http://geeks.ms/media/p/177541.aspx"&gt;aquí&lt;/a&gt; los materiales de la sesión, tanto las PPTs como los scripts que utilicé durante la misma.&lt;/p&gt;  &lt;p&gt;Gracias a todos los asistentes (muy buenas preguntas!) y por supuesto, a la organización de Mad.Nug :) &lt;/p&gt;  &lt;p&gt;Keep Rockin’!&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Rock Tip:&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;Podría tratar de encontrar algún rebuscado nexo entre el Rock Tip de hoy y el texto del post; como que espero que los consejos que compartí con vosotros os sirvan en alguna ocasión como ese ‘arcoíris en la oscuridad’ para resolver algún problema en vuestros entornos SQL Server, o que algunos de esos scripts valen más que el oro al otro lado del arcoíris…&lt;/p&gt;  &lt;p&gt;Pero esta vez no. Esta vez no necesito justificación para mi Rock Tip. Hoy se trata de un homenaje, y para eso no son necesarias excusas. Ronnie James Dio, la voz del heavy metal, el padre de la &lt;em&gt;mano cornuta&lt;/em&gt; (los cuernos heavies de toda la vida!) nos abandonó el día 16 del pasado mes. Un hombre que, pese a sus 67 años, seguía subiéndose a al escenario con una tremenda energía, con una voz asombrosa y sobre todo, con unas ganas de entretener y de ganarse el cariño de sus fans noche tras noche. &lt;/p&gt;  &lt;p&gt;Se nos ha ido un grande, pero está claro que será recordado siempre por su legión de seguidores desde los años 70, los chavales de hoy y, sin duda, los que vendrán.&lt;/p&gt;  &lt;p&gt;Aquí os dejo una versión del “&lt;a href="http://www.youtube.com/watch?v=1Sxc25uWqI8&amp;amp;feature=related"&gt;Rainbow In The Dark&lt;/a&gt;” en directo en el Wacken de 2004, donde podemos ver que el gran &lt;a href="http://es.wikipedia.org/wiki/Ronnie_James_Dio"&gt;Ronnie James Dio&lt;/a&gt; seguía estando como un chaval y disfrutando como si aún estuviera en Black Sabbath allá por 1979!&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://geeks.ms/aggbug.aspx?PostID=177557" width="1" height="1"&gt;</description><category domain="http://geeks.ms/blogs/palvarez/archive/tags/Eventos/default.aspx">Eventos</category><category domain="http://geeks.ms/blogs/palvarez/archive/tags/SQL+Server/default.aspx">SQL Server</category><category domain="http://geeks.ms/blogs/palvarez/archive/tags/Rendimiento/default.aspx">Rendimiento</category></item><item><title>Bang Bang!: Optimización Económica en SQL Server</title><link>http://geeks.ms/blogs/palvarez/archive/2010/05/25/bang-bang-optimizaci-243-n-econ-243-mica-en-sql-server.aspx</link><pubDate>Tue, 25 May 2010 20:41:00 GMT</pubDate><guid isPermaLink="false">2a2e7ade-7474-448b-9de5-1515d8bb7d1b:177282</guid><dc:creator>Pablo Alvarez</dc:creator><slash:comments>4</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://geeks.ms/blogs/palvarez/rsscomments.aspx?PostID=177282</wfw:commentRss><comments>http://geeks.ms/blogs/palvarez/archive/2010/05/25/bang-bang-optimizaci-243-n-econ-243-mica-en-sql-server.aspx#comments</comments><description>&lt;p&gt;Soy un impresentable. No hay manera de que actualice el blog, y cuando lo hago, es para promocionarme de mala manera. Y, como no podía ser de otra manera, esta es una de esas ocasiones :)&lt;/p&gt;  &lt;p&gt;Esta vez tengo que agradecer a &lt;a href="http://geeks.ms/blogs/gtorres/default.aspx"&gt;Gisela&lt;/a&gt;, &lt;a href="http://geeks.ms/blogs/lfraile/"&gt;Luis&lt;/a&gt; y el resto de chicos de Madrid.NET la oportunidad de pasar un ratito &lt;strike&gt;friki&lt;/strike&gt; serio, formal y productivo hablando de optimización &lt;em&gt;económica &lt;/em&gt;de SQL Server. La idea será aprender a localizar de dónde vienen los problemas, en lugar de tratar de optimizar las consultas en base a su &lt;em&gt;duration &lt;/em&gt;elevado de modo sistemático… vamos, la venerable metodología ASM (A Salto deMata(tm)).&lt;/p&gt;  &lt;p&gt;La primera parte del evento abordará las técnicas de revision que yo suelo emplear en mi trabajo en el equipo &lt;strong&gt;DOT&lt;/strong&gt; de Plain Concepts, mientras que la segunda parte se centrará en una serie de casos particulares de optimización que me encuentro en algunos clientes y que, a pesar de que alguno de ellos es muy frecuente, siguen siendo problemas bastante desconocidos para los que no nos pasamos delante de un SQL Server todo el dia :)&lt;/p&gt;  &lt;p&gt;Si el plan os resulta interesante o apetecible, ya sabeis.. ¡nos vemos el jueves! El evento será en las oficinas de Microsoft en La Finca, y los datos del registro los teneis en el siguiente enlace:&lt;/p&gt;  &lt;p&gt;&lt;a href="https://msevents.microsoft.com/CUI/EventDetail.aspx?EventID=1032451680&amp;amp;Culture=es-ES"&gt;https://msevents.microsoft.com/CUI/EventDetail.aspx?EventID=1032451680&amp;amp;Culture=es-ES&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Keep rockin’!&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Rock Tip: &lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;&lt;/strong&gt;Hoy me toca hacer algo que no me gusta, y que creo que solo hice en otra ocasión en este blog: voy a repetir un Rock Tip. Y voy a hacer esto por tres razones totalmente justificadas:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;El nombre es apropiado… vamos a hacer nuestro el lema de ’&lt;em&gt;Biggest Bang for the Buck&lt;/em&gt;’, así que el &lt;em&gt;Bang Bang!&lt;/em&gt; nos viene que ni pintado… &lt;/li&gt;    &lt;li&gt;Estamos hablando de la que posiblemente sea la mejor canción del mejor grupo de la historia. Punto pelota. xD &lt;/li&gt;    &lt;li&gt;El blog es mio y…. mmmm, creo que recurro con demasiada frecuencia a este punto :) &lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;Por todo esto, hoy le ha tocado el turno a &lt;a href="http://www.youtube.com/watch?v=doE883HTXpo"&gt;Bang Bang&lt;/a&gt;, un tema del primer disco de &lt;a href="http://en.wikipedia.org/wiki/Danger_Danger"&gt;Danger Danger&lt;/a&gt;. Si os apetece oir una cancion happy, que anima al instante, probadla. Como la gente que me conoce sabe de sobra, esta banda neoyorkina es mi grupo favorito, así que no me extenderé en detalles porque podría llegar a aburriros :)&lt;/p&gt;  &lt;p&gt;Si os interesa la musica de guitarristas para guitarristas, principalmente instrumental, no os perdais los trabajos en solitario de su guitarrista, &lt;a href="http://www.andytimmons.com/"&gt;Andy Timmons&lt;/a&gt;, que para mi es el guitar hero con mas estilo que ha parido madre :) Por cierto, el señor Timmons ha estado esta pasada semana en Madrid… así que también lo celebraremos con un temazo suyo: &lt;a href="http://www.youtube.com/watch?v=KaVVZYOvMFY"&gt;Cry for You&lt;/a&gt;.&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://geeks.ms/aggbug.aspx?PostID=177282" width="1" height="1"&gt;</description><category domain="http://geeks.ms/blogs/palvarez/archive/tags/Eventos/default.aspx">Eventos</category><category domain="http://geeks.ms/blogs/palvarez/archive/tags/SQL+Server/default.aspx">SQL Server</category><category domain="http://geeks.ms/blogs/palvarez/archive/tags/Rendimiento/default.aspx">Rendimiento</category></item><item><title>Patience: Ejecución de Tareas en SQL Server</title><link>http://geeks.ms/blogs/palvarez/archive/2008/08/10/patience-ejecuci-243-n-de-tareas-en-sql-server.aspx</link><pubDate>Sun, 10 Aug 2008 13:23:00 GMT</pubDate><guid isPermaLink="false">2a2e7ade-7474-448b-9de5-1515d8bb7d1b:92553</guid><dc:creator>Pablo Alvarez</dc:creator><slash:comments>5</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://geeks.ms/blogs/palvarez/rsscomments.aspx?PostID=92553</wfw:commentRss><comments>http://geeks.ms/blogs/palvarez/archive/2008/08/10/patience-ejecuci-243-n-de-tareas-en-sql-server.aspx#comments</comments><description>&lt;p&gt;En &lt;a href="http://geeks.ms/blogs/palvarez/archive/2008/05/18/these-days-un-d-237-a-en-la-vida-de-una-consulta.aspx"&gt;esta entrada&lt;/a&gt; estrenábamos una sección dedicada a la resolución de problemas de rendimiento en SQL Server, y os comentaba que tenía pensado dedicar éstas primeras entregas principalmente a la arquitectura del producto; mi idea es dedicar cada entrada a describir algunos de los conceptos determinantes para el rendimiento en la ejecución de nuestras consultas y acompañarlo de ejemplos prácticos. &lt;/p&gt;
&lt;p&gt;Pues bien, en esta ocasión &lt;em&gt;me gustaría hablaros&lt;/em&gt; de las esperas en SQL Server; un concepto fundamental de cara a la identificación de potenciales problemas de rendimiento. Y digo que me gustaría hablaros de las esperas, porque no voy a poder hacerlo :) Antes de ello, deberemos ver una introducción básica a la ejecución de tareas en SQL Server para posteriormente, en una entrada futura, introduciros a las esperas y a mi metodología de resolución de problemas de rendimiento favorita en SQL Server... (pero que bueno soy creando tensión y expectación, ¿eh? :) )&lt;/p&gt;
&lt;p&gt;Al turrón...&lt;/p&gt;
&lt;h4&gt;Ejecución de Tareas en SQL Server&lt;/h4&gt;
&lt;p&gt;El objetivo último de SQL Server es procesar tareas, y para ello necesita una serie de recursos (disco, memoria, red...), así como, al menos, un procesador. Hasta ahí nada nuevo. Lo que quizá no se conoce tanto es que SQL Server dispone internamente de unos procesadores lógicos llamados &lt;strong&gt;schedulers&lt;/strong&gt;, que van a ser clave a la hora de entender como se procesan las tareas, como interpretar las esperas y un sin fin de detalles relevantes para el rendimiento de nuestros servidores.&lt;/p&gt;
&lt;p&gt;Cuando SQL Server arranca, una de las primeras cosas que hace es consultar al sistema operativo, mediante WMI, cuantos procesadores tiene la máquina, para posteriormente crear tantos &lt;em&gt;schedulers &lt;/em&gt;como procesadores se hayan detectado. El objetivo de éstos será el de ejecutar las tareas del servidor (nuestras consultas, procesos internos de SQL Server como el &lt;em&gt;lazy writer&lt;/em&gt;, etc.), de un modo coordinado con el UMS/SQLOS del que hablaremos en otra entrada. &lt;/p&gt;
&lt;p&gt;Cada uno de esos procesadores lógicos tendrá asignada una lista de tareas, implementadas mediante &lt;em&gt;worker threads&lt;/em&gt;, que se ejecutarán a medida que el &lt;em&gt;scheduler&lt;/em&gt; vaya quedando libre. Estos &lt;em&gt;worker threads&lt;/em&gt; se implementan, por defecto, como hilos del sistema operativo (se puede cambiar este comportamiento para que funcionen como fibras, en caso de tener el servidor funcionando en &lt;em&gt;fiber mode&lt;/em&gt;), con su consiguiente consumo de memoria y recursos por cada uno de los hilos. Por esta razón, y para evitar un crecimiento masivo de hilos en el sistema, hay un parámetro de configuración del servidor llamado &lt;strong&gt;max_worker_threads&lt;/strong&gt;, que permite limitar el número máximo de hilos que SQL Server va a pedir al sistema operativo para ejecutar tareas en ellos. Éste será también el número máximo de tareas que va a tener el sistema, distribuido entre todos sus &lt;em&gt;schedulers&lt;/em&gt;.&lt;/p&gt;
&lt;h5&gt;Un par de ejemplos&lt;/h5&gt;
&lt;p&gt;&lt;a href="http://geeks.ms/blogs/palvarez/WindowsLiveWriter/LaFeatureAnteriormenteConocidaComoDMF_EB58/Sched0_2.png"&gt;&lt;img style="BORDER-TOP-WIDTH:0px;BORDER-LEFT-WIDTH:0px;BORDER-BOTTOM-WIDTH:0px;BORDER-RIGHT-WIDTH:0px;" height="276" alt="Scheduler para una única CPU" src="http://geeks.ms/blogs/palvarez/WindowsLiveWriter/LaFeatureAnteriormenteConocidaComoDMF_EB58/Sched0_thumb.png" width="163" align="right" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;El escenario más básico sería el que representamos en el diagrama de la derecha. En éste caso, disponemos de una máquina tradicional con una única CPU de un núcleo.&lt;/p&gt;
&lt;p&gt;El proveedor WMI devolverá que hay una CPU, y SQL Server creará un único &lt;em&gt;scheduler&lt;/em&gt; que gestionará la ejecución de todas las tareas del servidor. Habrá, por tanto, una única lista de tareas, con &lt;em&gt;n worker threads&lt;/em&gt;, donde &lt;em&gt;n&lt;/em&gt; es el número establecido en la variable de configuración &lt;strong&gt;max_worker_threads&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;El siguiente escenario (en el diagrama de abajo) representa un entorno con dos CPUs de doble núcleo. Como podemos ver, cada uno de las CPUs se enumera como dos CPUs diferentes por WMI (hay dos núcleos por cada CPU), por lo que tendremos un total de cuatro &lt;em&gt;schedulers&lt;/em&gt;. Por otra parte, vemos que habrá cuatro listas de ejecución de tareas, cada una de ellas con un limite máximo de una cuarta parte de las tareas máximas definidas por el &lt;strong&gt;max_worker_threads&lt;/strong&gt;. Esto es así porqué SQL Server trata de equilibrar la carga equitativamente entre todos sus procesadores.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://geeks.ms/blogs/palvarez/WindowsLiveWriter/LaFeatureAnteriormenteConocidaComoDMF_EB58/sched2_2.png"&gt;&lt;img style="BORDER-TOP-WIDTH:0px;BORDER-LEFT-WIDTH:0px;BORDER-BOTTOM-WIDTH:0px;BORDER-RIGHT-WIDTH:0px;" height="312" alt="sched2" src="http://geeks.ms/blogs/palvarez/WindowsLiveWriter/LaFeatureAnteriormenteConocidaComoDMF_EB58/sched2_thumb.png" width="540" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;Hay un tercer escenario interesante, pero me lo reservo para un poquito más adelante en este mismo post :) Eso si, no me gustaría acabar esta sección sin comentar un par de detalles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Una tarea no puede abandonar un &lt;em&gt;scheduler&lt;/em&gt; y moverse a otro a mitad de su ejecución. Esto es, una tarea esta atada a un &lt;em&gt;scheduler&lt;/em&gt; desde su creación hasta su finalización. 
&lt;li&gt;Se puede establecer una Mascara de Afinidad para determinar que CPUs vamos a usar en SQL Server, que schedulers vamos a crear, etc. pero esta fuera del ámbito de esta entrada por lo que no lo comentaremos de momento.&lt;/li&gt;&lt;/ul&gt;
&lt;h4&gt;Consultando los Schedulers&lt;/h4&gt;
&lt;p&gt;Bien, ya hemos visto que, en teoría, existe por ahí algo que se llaman &lt;em&gt;schedulers&lt;/em&gt;, que son CPUs lógicas que sirven para controlar la ejecución de tareas en SQL Server, y que además tienen forma de cajas de color verde con un suave y artístico degradado. Pero, sin duda alguna, en este instante os estáis preguntando si hay alguna manera de ver éstas estructuras en el servidor. Y también sé que, hasta que no descubráis como, no vas a poder conciliar el sueño. Por eso mismo, y porque no puedo tolerar el sufrimiento humano, pasamos sin más preámbulos a echar un vistazo a los metadatos que SQL Server expone para que veamos la información de los &lt;em&gt;schedulers&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;La siguiente query consulta sys.dm_os_schedulers, la DMV principal para obtener información acerca de los mismos:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;SELECT &lt;br /&gt;&amp;nbsp;&amp;nbsp; scheduler_id, &lt;br /&gt;&amp;nbsp;&amp;nbsp; cpu_id, &lt;br /&gt;&amp;nbsp;&amp;nbsp; status&lt;br /&gt;FROM sys.dm_os_schedulers&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Mi máquina de pruebas tiene una única CPU dual core. En ella, la ejecución de la consulta muestra el siguiente resultado:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://geeks.ms/blogs/palvarez/WindowsLiveWriter/LaFeatureAnteriormenteConocidaComoDMF_EB58/resultado1_2.png"&gt;&lt;img style="BORDER-TOP-WIDTH:0px;BORDER-LEFT-WIDTH:0px;BORDER-BOTTOM-WIDTH:0px;BORDER-RIGHT-WIDTH:0px;" height="164" alt="resultado1" src="http://geeks.ms/blogs/palvarez/WindowsLiveWriter/LaFeatureAnteriormenteConocidaComoDMF_EB58/resultado1_thumb.png" width="369" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;¿Y esto? ¿No deberían aparecer solo dos &lt;em&gt;schedulers&lt;/em&gt; según lo que hemos visto arriba? Bien, lo cierto es que solo los dos que aparecen marcados en naranja son &lt;em&gt;normales&lt;/em&gt;. Los dos que aparecen con estado HIDDEN ONLINE se emplean para procesar las tareas del sistema, mientras que los VISIBLE ONLINE se dedican al proceso de tareas de usuario, y son en las que nos fijaremos normalmente de cara a identificar potenciales problemas de rendimiento.&lt;/p&gt;
&lt;p&gt;El último scheduler, categorizado como VISIBLE ONLINE (DAC), esta reservado exclusivamente para la Dedicated Administrator Connection (Conexión Dedicada de Administrador). Simplificando mucho, SQL Server 2005 incorpora, por defecto, un nodo NUMA oculto, compuesto por una pequeña cantidad de memoria y un &lt;em&gt;scheduler&lt;/em&gt;, que nos permiten retomar el control del servidor para hacer troubleshooting básico del sistema en caso de que los &lt;em&gt;schedulers&lt;/em&gt; &amp;#39;normales&amp;#39; no estén disponibles o se encuentren monopolizados. &lt;/p&gt;
&lt;p&gt;El la práctica, lo habitual es filtrar la búsqueda para que no nos muestre ningún scheduler cuyo ID sea menor a 255, con lo que nos quedamos solo con los schedulers de usuario.&lt;/p&gt;
&lt;h4&gt;Complicando un poco más el asunto...&lt;/h4&gt;
&lt;p&gt;En realidad el modelo no es tan sencillo como una lista de tareas por scheduler. En SQL Server la planificación de tareas es bastante mas compleja, con una serie de listas adicionales. Nosotros vamos a ver a continuación un modelo un poco más real, introduciendo la lista de esperas por recursos (&lt;em&gt;resource waits&lt;/em&gt;). &lt;/p&gt;
&lt;p&gt;Si os fijáis en el diagrama que tenemos a continuación, vemos como el Scheduler contiene una tarea que se haya en estado &lt;em&gt;Running&lt;/em&gt; (en ejecución). También vemos una lista de tareas listas para ejecución (&lt;em&gt;Runnable Queue&lt;/em&gt;), que contiene las tareas que el Scheduler podría ejecutar en cualquier momento, ya que no dependen de recursos externos (disco, otras transacciones, etc.). Por último, tenemos la lista de esperas por recursos (&lt;em&gt;resource waits list&lt;/em&gt;) que contiene todas las tareas que aun no se han completado pero que no pueden pasar a la CPU porque dependen de una espera por un recurso externo, como pueda ser un bloqueo, operaciones de sincronización de paralelismo, una operación de disco o de red, etc. &lt;/p&gt;
&lt;p&gt;&lt;a href="http://geeks.ms/blogs/palvarez/WindowsLiveWriter/LaFeatureAnteriormenteConocidaComoDMF_EB58/Lists1_2.png"&gt;&lt;img style="BORDER-RIGHT:0px;BORDER-TOP:0px;BORDER-LEFT:0px;BORDER-BOTTOM:0px;" height="286" alt="Lists1" src="http://geeks.ms/blogs/palvarez/WindowsLiveWriter/LaFeatureAnteriormenteConocidaComoDMF_EB58/Lists1_thumb.png" width="525" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;En la siguiente entrada hablaremos de las esperas, y profundizaremos en este diagrama con ejemplos prácticos. Por ahora me conformo con haber introducido los conceptos que darán pie a hablar de las esperas en SQL Server.&lt;/p&gt;
&lt;h4&gt;Canzando Mitos: Hyperthreading vs. SQL Server&lt;/h4&gt;
&lt;p&gt;No se si os acordáis, pero en un post reciente (el término reciente en mi vida &amp;#39;bloggeril&amp;#39; es, como podréis apreciar, bastante relativo) hice un ejercicio descarado de plagio y posiblemente violé tres o cuatrocientas leyes al adoptar el tema de los &amp;#39;cazadores de mitos&amp;#39; para buscar un escenario práctico real en el que se aplicara la teoría vista en la entrada. Pues bien, vamos a seguir cazando mitos... en esta ocasión será el siguiente: ¿Realmente tiene sentido deshabilitar Hyperthreading(*) en nuestros servidores SQL Server?&lt;/p&gt;
&lt;p&gt;Vamos a ver lo que sucede en una máquina con una CPU con Hyperthreading &lt;a href="http://geeks.ms/blogs/palvarez/WindowsLiveWriter/LaFeatureAnteriormenteConocidaComoDMF_EB58/Sched3_2.png"&gt;&lt;img style="BORDER-TOP-WIDTH:0px;BORDER-LEFT-WIDTH:0px;BORDER-BOTTOM-WIDTH:0px;BORDER-RIGHT-WIDTH:0px;" height="363" alt="Sched3" src="http://geeks.ms/blogs/palvarez/WindowsLiveWriter/LaFeatureAnteriormenteConocidaComoDMF_EB58/Sched3_thumb.png" width="293" align="left" border="0" /&gt;&lt;/a&gt;habilitado. Vemos como tenemos una única CPU, con un único núcleo, y a pesar de todo, SQL Server crea dos &lt;em&gt;schedulers. ¿Por que hace esto SQL Server? &lt;/em&gt;Pues sencillamente porque, como os comenté, SQL Server le pregunta a Windows, a través de WMI, cuantos procesadores tiene la máquina... y no sé quien tomo la decisión de diseño (o quién pagó a quien, ni cuanto...) pero el caso es que para Windows, una máquina con Hyperthreading habilitado cuenta como si tuviera dos CPUs.&lt;/p&gt;
&lt;p&gt;Nosotros, que somos chicos y chicas listos, sabemos que no es cierto; sabemos que el Hyperthreading es, simplificando mucho, una mejora en en las Execution Units(EU) del procesador que permiten que, en ciertas circunstancias, se ejecute trabajo en estas EU que en otros casos estarían ociosas. &lt;strong&gt;Pero&lt;/strong&gt; (y éste es un gran pero...) realmente solo hay una CPU: las diferentes EUs comparten, entre otras cosas, las cachés de procesador L1 y L2. Y hete aquí que tales cachés son importantes en SQL Server, hasta el punto de que si se comparten puede haber serios problemas en dos partes fundamentales del servidor: el mecanismo de agujas del reloj del &lt;em&gt;lazy writer&lt;/em&gt; (que sirve para sacar del Buffer Pool las páginas menos utilizadas), y el mecanismo para adquirir spinlocks (bloqueos livianos que se solicitan mediante polling constante). Al compartir las caches, es muy posible que en operaciones paralelizadas las cachés no tengan la información que deben de tener, lo que puede provocar que se saquen del Buffer Pool páginas que no deberían irse fuera (con su consiguiente pérdida de rendimiento), o peor aún, que no se pueda adquirir un spinlock.&lt;/p&gt;
&lt;p&gt;Sin entrar en demasiado detalle, quedémonos con la idea de que SQL Server ve dos procesadores, y crea dos schedulers, pero en realidad solo es un &amp;#39;truco&amp;#39;, interesante en otro tipo de aplicaciones pero muy dañino para SQL Server. ¿Conclusión? Al menos en SQL Server 2000 y 2005 os recomiendo encarecidamente deshabilitar Hyperthreading desde la BIOS si vuestros servidores lo soportan. No se aún si SQL Server 2008 implementa algún cambio que afecte a servidores Hyperthreaded, pero en cuanto consiga hacerme con una máquina con un procesador apropiado haré la prueba.&lt;/p&gt;
&lt;p&gt;(*): &lt;em&gt;Ahhh... hyperthreading, aquella tecnología que &lt;/em&gt;&lt;a href="http://news.zdnet.co.uk/hardware/0,1000000091,39286539,00.htm"&gt;&lt;em&gt;amenaza&lt;/em&gt;&lt;/a&gt;&lt;em&gt; con volver a nuestros procesadores :_) ¡que malo soy a veces con los chicos de Intel.&lt;/em&gt;&lt;/p&gt;
&lt;h4&gt;Conclusiones&lt;/h4&gt;
&lt;p&gt;Bueno, nos ha salido otro post un poco &amp;#39;ladrillo&amp;#39;, pero hemos sentado una buena base a la que nos referiremos en posteriores entradas. Lo más importante aquí es comprender lo que son los &lt;em&gt;schedulers&lt;/em&gt;, tener una aproximación a las diferentes listas de tareas y ver como podemos estudiar los metadatos para hacernos una idea de si el sistema esta sufriendo de contención de CPU o no. &lt;/p&gt;
&lt;p&gt;Evidentemente en este artículo hemos realizado algunas simplificaciones (sobre todo en el ámbito de las diferentes listas de tareas), pero en siguientes entradas entraremos en más detalle, y si no me equivoco empezaremos precisamente con las esperas de las diferentes tareas, los estados que pueden tener y como pasan de uno a otro y, sobre todo, cómo podemos utilizar esa información para comprender por donde se nos escapa el rendimiento. &lt;/p&gt;
&lt;p&gt;Espero que os resulte interesante :)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Rock Tip:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Ya os dije en la presentación de esta entrada que vamos a tener que tener un poco de paciencia antes de poder saltar a las esperas de SQL Server, por lo que me ha parecido apropiado preceder el titulo de este post con el temazo &lt;a href="http://www.youtube.com/watch?v=pEzuC5UoM8g"&gt;&amp;#39;Patience&amp;#39;&lt;/a&gt; de los &lt;a href="http://en.wikipedia.org/wiki/Guns_n%27_roses"&gt;Guns n&amp;#39; Roses&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Hablando de los Guns N&amp;#39; Roses (que, por cierto, son/fueron uno de mis grupos favoritos), ahí va mi órdago: sí el señor &lt;a href="http://en.wikipedia.org/wiki/Axl_Rose"&gt;William &amp;#39;Axl&amp;#39; Rose Jr.&lt;/a&gt; y sus secuaces (sean quienes sean) sacan el &lt;a href="http://en.wikipedia.org/wiki/Chinese_Democracy"&gt;&amp;#39;Chinese Democracy&amp;#39;&lt;/a&gt; durante éste año del señor de 2008, un servidor se corta el pelo. ¿Mi predicción? El disco será la banda sonora del Duke Nukem Forever xD&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://geeks.ms/aggbug.aspx?PostID=92553" width="1" height="1"&gt;</description><category domain="http://geeks.ms/blogs/palvarez/archive/tags/SQL+Server/default.aspx">SQL Server</category><category domain="http://geeks.ms/blogs/palvarez/archive/tags/Rendimiento/default.aspx">Rendimiento</category></item></channel></rss>