Los resultados de tu query NO se guardan en memoria en SQL Server!
Published: 2022-08-08 02:57:35

Holaa, este es el primero post sobre una serie de temas relacionados a SQL Server para Business Inteligence en el 2022, (si eres alguien experto en SQL, estoy seguro que al menos algo nuevo aprenderas de esto)  ya que practicamente casi cualquier
sistema que existe en todo el mundo, se mueve por alguna base de datos asi que esta serie de post ayudarà aquellos que les interese incursionarse en este mundo. 


Para esto estoy utilizando la base de datos que brinda Microsoft 
[AdventureWorks2019.](https://docs.microsoft.com/en-us/sql/samples/adventureworks-install-configure?view=sql-server-ver16&tabs=ssms)

---

Observamos la tabla FackProductInventory, tiene dos primary key (ProductKey, DateKey) que tambien es un clustered index y de acuerdo a cómo definas este índice,  se guardará la información en el disco.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/102egz4dmtwl9veodmh1.PNG)

Como mencioné en el título del post, SQL no guarda ni filas ni columnas, lo que guarda son solo páginas de 8 KB,  ordenado según el clustered index creado por el primary key.

 
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zff51uzcr6rgwhiqxz79.png)
En teoría en cada una de estas páginas de 8kb guarda todas las columnas de la tabla, sin embargo a veces tenemos campos con el tipo de dato nvarchar(max) donde el contenido de ese campo no se puede guardar en una pagina de 8k asi que lo que hace SQL es guardar un puntero de ese campo y lo guarda en otra pagina de 8 kb.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cnu0moj6nqv93k9il6mm.png)

Entonces, la base de datos no es mas que una agrupación de estas paginas de 8kb, asi que SQL Server no busca "una fila" ni " una columna" y cuando quiere encontrar, insetar, actualizar algo en especifico, SQL Server debe "descubrir" en qué pagina 8kb
la data esta guardada, lo pone en memoria, hace el cambio que se solicita al registro y lo regresa nuevamente al disco. 


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i2hpl75es9m3tsvsnnt8.png)
En un mundo ideal, SQL Server sabe exactamente donde se guarda la informacion y solo lee esa única pagina. Pero la realidad es otra y SQL tiene que scanear toda la tabla para realizar todo el trabajo.

- Usualmente nuestras querys no son fáciles de entender (a veces no usan la clausulas where  y traen toda la data)
- Tenemos desordenada la informacion en diferentes paginas 
- SQL tiene que lidiar con diferentes operaciones como agroupamientos, joins, ordenamientos entre otros.

---

## **CASO PRÁCTICO**
Ejecutamos el siguiente comando para activar cuantos bytes lee la query en memoria y disco.

```
SETSTATISTICS IO ON;
```
Vemos que la query lee 3860 logicalreads que son las lecturas que realiza en memoria y 574 physical reas que son lecturas que realiza en disco y todo esto es al rededor de 35.3MB data que lee para ejecutar esta query. 

Generalmente, mientras mas data tu query tiene que leer más lento hace tus querys  en este caso como no hay ningun filtro SQL tiene que leer cada fila e ir gritando en voz alta (SELECT) el campo DateKey, en este caso fueron 776,286 registros


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8gikcp2h2a4l4mctrqd3.png)
Ahora agregamos un simple filtro a la consulta y vemos nuevamente el plan de ejecución:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/677ou5bnowrxtj0nwb1j.png)
Un segundo.. ¿es elmismo plan de ejecución de la consulta anterior? 

Asi la consulta devuelva MENOS data, no significa que SQL haga menos trabajo y antes de que se ejecuté la query, SQL tiene que "predecir" cuanto esfuerzo se va a requerir y para ello utiliza uno de los indicadores "Estimated Subtree Cost" basado
en IO y CPU; para esta query, SQL estima que costará 3.7 querySoles


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/awz983oeva40dqnvd9p0.png)

Agregarmos un ordenamiento por el campo MovementDate, y ahora detenganse un momento para pensar en la siguiente imagen y preguntensé, como ser humanos, ¿cómo realizarían la siguiente consulta? 


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4fuc6awhmjn6s2bcg63c.png)

En cristiano, como ser humano, leeriamos cada página e iriamos anotando en otra los DateKey y MovementDate que encontremos, una vez terminada esa tarea, empezariamos a ordenarlos por la fecha.  En el plan de ejecución cada operador es como si fuera
un microservicio que generalmente solo realizan una tarea. 

En este caso observamos que por agregar el ordenamiento el costo total se multiplicó x6 !! Eso es criminal. Todo se debe a que SQL necesita mas paginas 8KB para escribir los resultados para ordenarlos y en un mundo perfecto lo hace todo en memoria pero
en un mundo imperfecto no habrá suficiente memoria y lo hace en disco, por eso es importante que nuestras querys sean lo mas humanamente entendibles para que SQL pueda predecir cuanta memoria le asignará a tu consulta. Por que una vez SQL le asigna
memoria al inicio de la ejecución ese número se queda grabado en piedra.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7k1nslldw3trhmauxz5c.png)

En ocasiones cuando SQL no estima bien cuanta memoría va a necesitar, aparecen advertencias en el plan de ejecución donde SQL derrama data en tempdb para poder ordernar o realizar el resto de operaciones de la query.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ds4gbtgq1a57lbjibl0n.png)
Cómo último reto, sin mirar el plan de ejecución, la siguiente query demorá más en leer data? o en escribir la data? o en ordenar la data? o en mostrar los resultados?


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/klcb6uk8qjaxcjxgyof6.png)
Ya que ahora estamos trayendo todos los campos de la query eso significa que vamos a leer mas paginas 8KB y a su vez tendremos que escribir en más páginas 8KB y como será mas data en memoría, el ordenamiento también se volverá pesado.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0y77nwb7k4r2oedzemsa.png)
Y para ser honestos personalmente, no  importa si utilizas SELECT * siempre y cuando no uses ORDER BY, ya que en SQL es el segundo lugar más caro para ordernar data.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6nakbmzoernfuvymds1h.png)
Ahora imagina que cientos de usuarios esten ejecutando esa misma query una y otra vez  al mismo tiempo, va a generar una sobrecarga en RAM del servidor y SQL no podra guardar todo en cache y todo explotará (más o menos)

---

## **EN RESUMEN, HEMOS APRENDIDO**

> Para ver cuantas paginas 8KB utiliza nuestra query, debemos activar SET STATISTICS IO ON; (No hace daño activarlo)
> Filtrar un campo sin que este indexado, SQL siempre hará un table scan.
> La sentencia ORDER BY sin soporte de indices, terminará consumiendo mas ram de lo estimado.
> SQL no pone en memoria los resultados de la query pero si pone en memoria las paginas 8KB.


## **EN EL SIGUIENTE POST**

Veremos como solucionar el problema de costos con nonclustered indexes y la diferencia entre los operadores index seek & scans table.