Mes: julio 2025

SQL contra el Apocalipsis Mutante (Parte 1): Aprende SQL y salva el mundo

Las ciudades están en ruinas. Las carreteras, bloqueadas por coches abandonados. El olor a pólvora y carne podrida se mezcla en el aire mientras hordas de mutantes se extienden sin control. Los pocos refugios que aún se mantienen en pie dependen de un único recurso para resistir un día más: los datos de nuestra base de datos SurvivalDB. Y ahí entramos nosotros, con un SQL Server medio polvoriento pero funcionando como un reloj.

En este primer episodio de nuestra serie para aprender a analizar datos en SQL no vas a encontrar dashboards color pastel ni entornos de BI con animaciones, el mundo se acaba y no hay tiempo para eso. Solo tenemos datos crudos, queries eficientes y la urgencia de sobrevivir. Hoy vamos a plantear una serie de ejercicios básicos que te permitirán dominar SELECT, WHERE y JOIN, mientras ayudamos a los últimos supervivientes a organizar sus suministros, identificar amenazas y evitar convertirse en croquetas mutantes.

El escenario: SurvivalDB, el corazón de la resistencia

En nuestro mundo postapocalíptico, la base de datos SurvivalDB es el último bastión de la civilización. Contiene cuatro tablas esenciales que almacenan la información crítica para la resistencia:

  • RefugeSupplies: el inventario de cada refugio, incluyendo comida, agua, armas y ubicación geográfica (latitud y longitud). Saber qué refugio tiene recursos y cuál se muere de hambre es la diferencia entre enviar ayuda o enterrar cadáveres.
  • SurvivorStats: registra la población de cada refugio y el número de infectados. Porque nada genera más pánico que un refugio lleno de supervivientes a punto de convertirse en mutantes.
  • MutantSightings: almacena los avistamientos de hordas de mutantes con fecha y localización. Analizar estos datos permite anticipar ataques y planificar rutas seguras para los supervivientes.
  • EvacuationRoutes: describe las rutas de evacuación posibles entre refugios, indicando desde qué refugio se parte y a cuál se llega. Es vital para planificar la huida cuando la defensa ya no es una opción y sólo queda correr.

Te dejo el script para que crees tú mismo la base de datos, las tablas y los datos que vamos a usar en los ejercicios.

Ejercicios: cada consulta es un capítulo en la lucha por la supervivencia

El destino de los supervivientes depende de cómo consultemos estos datos. Un SELECT rápido puede significar reforzar un refugio a tiempo; un SELECT lento puede significar que ya es demasiado tarde. Comete cualquier error en tus consultas y podrías enviar suministros al refugio equivocado o ignorar un refugio al borde del colapso.

Ejercicio 1: Localiza los refugios al borde del colapso

Tras semanas sin recibir suministros, varios refugios podrían quedarse sin comida ni agua en cualquier momento. Si caen estos refugios, los supervivientes saldrán huyendo y propagarán el caos… o algo peor.

Tu misión: listar los refugios con menos de 10 raciones de comida o menos de 50 litros de agua. Tenemos que conocer el RefugeID, FoodRations y WaterLiters, ordenados de menor a mayor por FoodRations. Al ser el primero este será el único ejercicio con respuesta incluida como ejemplo.

Ejercicio 2: Identifica a los refugios mejor armados

Tras localizar los refugios en situación crítica, el Consejo necesita saber quiénes pueden apoyarlos. Los refugios con más armas son los únicos capaces de enviar ayuda o resistir un asedio prolongado.

Tu misión: mostrar los cinco refugios con mayor número de armas (Weapons), mostrando también RefugeID. Piensa cómo limitar los resultados para centrarte en los más fuertes.

Pista: Ordena tus datos de forma que los primeros sean los más peligrosos… o los más protegidos.

Ejercicio 3: Define la zona caliente del mapa

Los exploradores acaban de interceptar señales de radio que indican grandes movimientos de hordas entre las latitudes 39 y 41 y longitudes -75 y -73. Hay que comprobar si los refugios críticos o mejor armados están dentro de esa zona de peligro inminente.

Tu misión: listar los refugios ubicados en ese rango, mostrando RefugeID, Latitude y Longitude.

Pista: Dos coordenadas definen un área. ¿Cómo filtrarías para quedarte solo con los refugios dentro de esa caja imaginaria?

Ejercicio 4: Conoce a los supervivientes que defiendes

La información reunida hasta ahora es valiosa, pero incompleta. Necesitamos saber cuántos habitantes hay en cada refugio para calcular si tienen suficiente comida, agua y armas para sobrevivir. Sin estos datos combinados, cualquier estrategia será un tiro en la oscuridad.

Tu misión: combinar RefugeSupplies y SurvivorStats para mostrar RefugeID, Population, FoodRations y Weapons de cada refugio.

Pista: La información de suministros y la población viven en tablas distintas, pero tienen algo en común: el refugio.

Ejercicio 5: Identifica tragedias inminentes

El análisis anterior revela un peligro alarmante, algunos refugios tienen mucha gente pero apenas armas. Si un mutante llega, la masacre será inmediata y el caos se extenderá a otros refugios.

Tu misión: encontrar los refugios que tienen menos de 5 armas y más de 50 supervivientes. Estas son las prioridades absolutas para enviar refuerzos antes de que el desastre sea irreversible.

Pista: Para detectar refugios sobrepoblados y mal armados, debes comparar datos que no están en la misma tabla. ¿Cómo unirlos y luego aplicar las condiciones?

Consejo de veterano

En producción, la diferencia entre una consulta optimizada y una chapuza es la misma que entre un refugio reforzado y uno que se desmorona al primer ataque. Indexa, revisa planes de ejecución y parametriza: no estamos jugando, estamos sobreviviendo.

¿Quieres las respuestas?

Estoy preparando un vídeo en el canal de YouTube donde resolveremos paso a paso estos ejercicios y explicaremos cada detalle para que no sólo los copies, sino que los entiendas. Eso sí, tendrás que esperar a la semana que viene, mientras tanto intenta resolver estos ejercicios por tu cuenta para practicar y deja en comentarios tus respuestas.

Próximamente…

En el siguiente episodio subiremos la dificultad: cálculos de tasas de infección, clasificaciones y agrupaciones para decidir a quién ayudar primero. Porque en un mundo arrasado por mutantes, saber sumar y agrupar correctamente puede ser lo que te salve.

Publicado por Roberto Carrancio en Cloud, SQL Server, 9 comentarios

Page Split: el invitado indeseado en SQL Server

Hay conceptos en SQL Server que son como ese compañero de trabajo que nadie ha invitado a la reunión pero que aparece, opina y encima se queda a tomar café. Los Page Split son ese compañero. Y lo peor de todo es que, en muchos casos, somos nosotros los que, sin querer, les abrimos la puerta. En este artículo vamos a diseccionar qué son, por qué ocurren, cómo detectarlos y, sobre todo, cómo podemos hacerles la vida difícil para que molesten lo menos posible.

Qué demonios es un Page Split

Cuando hablamos de Page Split en SQL Server nos referimos a un fenómeno que ocurre cuando el motor de base de datos necesita insertar un registro en una página de datos (o de índice) que ya está llena. Como SQL Server trabaja con páginas de 8 KB (sí, esa unidad tan entrañable que nos acompaña desde hace décadas), llega un momento en el que no hay espacio para ese nuevo registro. Entonces, el motor se ve obligado a dividir la página en dos: mueve aproximadamente la mitad de las filas a una nueva página y deja espacio en la original para la nueva inserción. Esto es el famoso Page Split.

Hasta aquí podría parecer un mecanismo inteligente y útil. Y lo es, en teoría. El problema es el impacto que tiene: incremento del número de páginas (y por tanto del tamaño del índice), fragmentación lógica, más operaciones de I/O, y un rendimiento que empieza a cojear como un servidor sin mantenimiento en una década.

Cuándo y por qué ocurre el Page Split

La causa principal de un Page Split es el uso de claves que no son secuenciales en índices clustered o nonclustered. Insertar valores intermedios en el orden lógico del índice fuerza al motor a abrir hueco donde no lo hay. Por ejemplo, si tenemos un índice clustered sobre un campo Nombre y estamos insertando valores aleatorios, SQL Server tendrá que hacer malabarismos para mantener el orden lógico.

Claro, alguno podría pensar: “Esto con un índice clustered en una clave IDENTITY no pasa”. Efectivamente, las claves secuenciales como IDENTITY, NEWSEQUENTIALID() o datetime en inserciones crecientes suelen evitar la mayoría de los Page Split. Pero ojo: incluso con claves secuenciales, si empezamos a hacer updates que ensanchan las filas más de lo que la página puede aguantar, también podemos provocar splits. Porque sí, los Page Split no son sólo cosa de INSERT, también los UPDATE pueden invitar al desastre si agrandan las filas.

Cómo afectan al rendimiento (y al ánimo del DBA)

Los Page Split producen fragmentación interna y externa. Interna porque dejan huecos en las páginas; externa porque el orden lógico del índice ya no se corresponde con el orden físico. Esto se traduce en más lecturas para recuperar los datos, más uso del log de transacciones y, en general, más I/O. Y todos sabemos que el disco es, casi siempre, el cuello de botella por donde mueren los sueños de un servidor bien optimizado.

Por si fuera poco, cada Page Split genera actividad en el log de transacciones: hay que registrar la creación de la nueva página, el movimiento de filas y la actualización de punteros. Si tenemos una base de datos en modo FULL, el crecimiento del log puede ser un espectáculo digno de verse (si uno disfruta con los desastres).

Cómo detectar Page Split

Como sabes, lo que no se mide no se puede optimizar, lo bueno es que detectar los Page Split no es complicado si sabemos dónde mirar. Un sitio clásico es el contador de rendimiento SQLServer:Access Methods – Page Splits/sec. Si ese contador empieza a dar cifras escandalosas, es momento de investigar.

Otra opción es recurrir a las DMV. La vista sys.dm_db_index_physical_stats nos permitirá ver el grado de fragmentación de los índices. Un índice muy fragmentado suele ser un buen candidato a tener Page Split en su historial reciente. Y si queremos verlos en tiempo real, un Extended Event como page_split o sqlserver.page_split, según versión, nos dará información jugosa sobre cuándo y dónde están ocurriendo.

Sí, podemos hacerlo también con Profiler, pero sinceramente, a estas alturas quien siga usando Profiler en producción para esto se merece un Page Split en su jornada laboral.

Cómo prevenir los Page Split

Es mejor prevenir que curar, siempre. La primera y más efectiva estrategia es diseñar bien las claves de los índices. Un índice clustered sobre un campo secuencial es nuestro mejor amigo. Esto evita la mayor parte de los Page Split derivados de inserciones.

Otra técnica clásica es jugar con el FILLFACTOR. Cuando creamos o reconstruimos un índice podemos indicar un valor de Fillfactor que deje un porcentaje de espacio libre en las páginas (por ejemplo, un 90% en vez del 100% por defecto). Esto da margen a que nuevas inserciones se acomoden sin provocar un split inmediato. Claro, no es gratis: el índice ocupará más espacio y necesitaremos más memoria y más I/O. Pero al menos los splits no nos atacarán a traición.

Eso sí, cuidado con el Fillfactor. Poner un Fillfactor bajo indiscriminadamente es como invitar al primo gorron a casa: puede aliviar al principio pero al final nos pasa factura. Hay que medir, probar y ajustar.

Y no olvidemos las operaciones de mantenimiento: reconstruir o reorganizar los índices periódicamente ayuda a reducir la fragmentación causada por los Page Split. Aquí no vamos a descubrir nada nuevo, pero sí recordar lo evidente: el mantenimiento es como lavarse los dientes; si no lo haces, el problema llegará y será feo.

Casos reales y conclusiones claras

En entornos OLTP con claves no secuenciales he llegado a ver índices con más del 70% de fragmentación en menos de una hora de actividad. La consecuencia era un aumento de I/O y un rendimiento que hacía llorar al más curtido. ¿La solución? Cambio de diseño de clave, ajuste del Fillfactor y un plan de mantenimiento digno.

En entornos con cargas masivas de datos desordenados, lo ideal es insertar en tablas heap (sin clustered) y luego aplicar el índice clustered al finalizar, o bien usar particiones y cargas ordenadas. Porque sí, hay vida más allá de pelearse con Page Split en cada carga.

En definitiva, los Page Split son inevitables en determinados escenarios, pero podemos hacer mucho para mantenerlos a raya. El diseño de los índices, el Fillfactor y un mantenimiento adecuado son nuestras principales armas. Y no, el truco no está en hacer rebuilds cada noche a lo loco; está en entender el porqué y el cuándo, y actuar con cabeza.

Si tenéis alguna duda o sugerencia, podéis dejarla en Twitter, por mail o dejarnos un mensaje en los comentarios. Y recuerda que también tenemos un grupo de LinkedIn y un canal de YouTube a los que te puede unir. ¡Hasta la próxima!

Publicado por Roberto Carrancio en Cloud, Rendimiento, SQL Server, 0 comentarios

El problema del Reenvío de punteros en tablas heap

Hoy vamos a hablar de un fenómeno tan molesto como habitual en ciertos entornos: el reenvío de punteros en tablas heap de SQL Server. Sí, ese “detalle” que suele pasar desapercibido hasta que un día nuestras consultas empiezan a ir como un carro tirado por burros, y claro, toca ponerse el traje de bombero.

Antes de entrar en harina, pongamos un poco de contexto. Las tablas heap, esas nobles estructuras sin índice clustered, a veces se eligen por necesidad, otras por ignorancia y, en los peores casos, porque alguien tenía prisa por entregar y pensó que ya lo arreglaríamos después.Y claro, luego nunca se arregla. Y mientras tanto, los forwarded records o reenvíos de punteros campan a sus anchas.

¿Qué es un reenvío de punteros y por qué debería importarnos?

Cuando una tabla no tiene índice clustered, SQL Server guarda las filas donde buenamente puede. Sin ese ordenamiento y estructura que un clustered impone, el heap es un campo abierto. Hasta aquí, todo bien. El problema aparece cuando actualizamos una fila y esa nueva versión no cabe en la misma página. ¿Qué hace SQL Server? ¿Mover toda la fila a otra página y actualizar los punteros que la referencian? Ojalá. Lo que hace es dejar un puntero en la ubicación original que apunta a la nueva ubicación de la fila. Ese es el reenvío de puntero: un salto innecesario que se añade al acceso de la fila.

Esto, que puede parecer inofensivo en pequeñas dosis, se convierte en un verdadero problema cuando la tabla crece y las modificaciones son frecuentes. Cada reenvío implica un acceso extra a disco o memoria para encontrar la fila real. Y como bien sabemos, esos accesos extra no son gratuitos: incrementan el tiempo de lectura y degradan el rendimiento de las consultas. Especialmente en esas tablas que alguien decidió consultar con un SELECT * y sin WHERE, porque total, ¿qué podría salir mal?

Cómo se genera un reenvío de punteros

Veámoslo en acción. Tenemos una tabla heap. Insertamos filas. Todo perfecto. Llega el día en que un UPDATE aumenta el tamaño de una fila (añadimos datos a una columna VARCHAR, por ejemplo). La fila ya no cabe en su página. SQL Server mueve la fila a una nueva página y deja un puntero en la posición original apuntando a la nueva ubicación. Algo así como el cartel de nos hemos mudado que ponen algunos negocios en su antiguo local cuando cambian de ubicación. Ahora acceder a esa fila implica primero leer la página con el puntero, luego saltar a la nueva página y leer la fila real. Siguiendo con el ejemplo anterior es como si el GPS te llevase al local antiguo para que vieses el cartel y tuvieses que ir al nuevo.

Pero es que la fiesta no termina ahí. Si la fila se vuelve a actualizar y tampoco cabe en su nueva página, obtenemos un reenvío de un reenvío. ¿Bonito, verdad? Sí, tan bonito como ese fragmento lógico que nadie defragmenta porque “total, no pasa nada”.

Impacto en el rendimiento de los reenvíos de punteros

El impacto de los reenvíos de punteros se manifiesta principalmente en las operaciones de lectura. Cada reenvío supone al menos un salto adicional. Si tenemos un número considerable de ellos, nuestras lecturas se convierten en una gincana de páginas de datos, con sus correspondientes cache misses, latches y demás alegría. Pero no creas que las escrituras están exentas, recuerda que antes de escribir casi siempre lees.

Esto no solo afecta a la velocidad de las consultas. También incrementa el uso de CPU, el tráfico de I/O y la presión sobre el buffer pool. Y aquí viene el detalle que más nos gusta: el optimizador de consultas no tiene en cuenta el coste de los reenvíos al planificar. Así que podemos tener un plan que parecía estupendo sobre el papel y que en ejecución se arrastra como un SELECT con hints absurdos.

Cómo detectar el reenvío de punteros

Detectar reenvíos de punteros no es complicado, pero requiere mirar donde hay que mirar. Podemos usar sys.dm_db_index_physical_stats con la opción DETAILED para analizar las tablas heap y ver el número de forwarded_records. Si este número empieza a crecer, es hora de preocuparse.

Un ejemplo de consulta para los amigos de lo rápido y directo:

Esto nos da una idea de cuántos reenvíos tenemos por tabla. Si ese número no es cero, tenemos trabajo pendiente. Y si el número es alto, es probable que también tengamos un problema de rendimiento.

¿Como elimino los reenvíos de punteros?

La primera solución (y la más eficaz) es: no uses heap salvo que tengas un motivo sólido para hacerlo. Si la tabla tiene lecturas frecuentes y actualizaciones que modifican el tamaño de las filas, un índice clustered es casi siempre mejor elección. Sí, incluso aunque esa tabla sea de staging y “solo esté ahí un ratito”.

Si por algún motivo tenemos que seguir con el heap, toca plan de mantenimiento especial. Por norma general, los planes de mantenimiento de índices no nos van a servir. En su lugar, tenemos que usar el comando ALTER TABLE [NombreTabla] REBUILD que reconstruye el heap y elimina los reenvíos de puntero. Pero claro, eso implica bloqueo de tabla (en algunas versiones de SQL) y no es algo que uno quiera hacer en mitad de un horario productivo.

Otra opción es rediseñar el esquema de la tabla para minimizar las columnas de longitud variable que crecen sin control. Porque claro, si tenemos un VARCHAR(8000) para guardar un número de teléfono “por si acaso”, el problema no es el heap: el problema somos nosotros.

Y por supuesto, monitorizar. Tener un job que periódicamente revise el número de forwarded_records y alerte si se dispara es una medida sencilla que nos puede ahorrar muchas sorpresas.

¿Hay situaciones donde el heap tenga sentido?

Sí, existen. Por ejemplo, en tablas de staging para cargas de datos donde no se hacen actualizaciones y los datos se procesan y eliminan rápidamente. Ahí un heap puede funcionar bien. Pero claro, en cuanto se empieza a hacer algún UPDATE, la decisión de no poner un clustered se vuelve difícil de defender. Y no, el argumento de “es que así insertamos más rápido” no vale si luego el rendimiento de las consultas se va al traste.

Conclusión

El reenvío de punteros es un mecanismo interno de SQL Server para lidiar con el crecimiento de filas en tablas heap. No es un bug, es un diseño consciente. El problema es que suele aparecer porque tomamos decisiones de diseño pobres o porque descuidamos el mantenimiento. Si tenemos un heap, debemos ser conscientes de las implicaciones y monitorizar su estado. Y si vemos reenvíos, actuar antes de que el rendimiento se desplome.

Como siempre, lo más efectivo es evitar el problema desde el principio. Un índice clustered bien elegido elimina de raíz el riesgo de reenvíos. Y si alguien nos insiste en que un heap es la mejor opción para esa tabla con 500 millones de filas y actualizaciones frecuentes, siempre podemos sugerirle que haga las pruebas en producción. Total, ¿qué podría salir mal?

Espero que este artículo te haya resultado útil e interesante. Si tienes alguna duda o comentario, no dudes en contactarnos en Twitter o por mail o dejarnos un mensaje en los comentarios de aquí abajo. Y recuerda que también tenemos un grupo de LinkedIn al que te puedes unir.

Publicado por Roberto Carrancio en Cloud, Rendimiento, SQL Server, 0 comentarios