SQL Server performance: ORDER BY y consumo de CPU

Cuando escribimos nuestro código T-SQL en algunas ocasiones se observa la instrucción ORDER BY la cual técnicamente lo que hace es ordenar el set de datos resultantes y a menos que exista un TOP lo único que se logra es solamente una presentación de los datos.

Ahora bien, ¿ sabemos el impacto que esto tiene en la performance y el consumo de recursos ? , en este post te voy a mostrar que es lo que sucede cuando aplicas un ordenamiento y cuales serian las posibles alternativas de solución.

Ejemplo 1

Vamos a suponer que tenemos las siguientes consultas T-SQL

SELECT * 
FROM PURCHASING.PURCHASEORDERHEADER 

SELECT * 
FROM PURCHASING.PURCHASEORDERHEADER 
ORDER BY ORDERDATE

Si observamos sus planes de ejecución nos encontraremos el siguiente resultado:

Nota: Como se puede observar la segunda consulta tiene una operación adicional y además han aumentado sus costos en consumo de recursos (sobre todo CPU) con relación a la primer consulta.

Ejemplo 2

Supongamos los dos siguientes ejemplos en donde armamos una consulta pero ahora con un WHERE sobre un campo que tiene índices en la tabla.

SELECT * 
FROM PURCHASING.PURCHASEORDERHEADER 
WHERE EMPLOYEEID = 25

SELECT * 
FROM PURCHASING.PURCHASEORDERHEADER 
WHERE EMPLOYEEID = 25
ORDER BY ORDERDATE

Si observamos los planes de ejecución nos encontraremos con algo similar al ejercicio anterior

Nota: Podemos observar que por mas que se use el índice nos aparece en la segunda consulta el operador de SORT

Como evitar el operador SORT

La única forma de evitar este operador es creando un índice que contenga las columnas (claro la pregunta es si vale la pena hacer esto cuando en muchos casos es solo por presentación el uso de ORDER BY)

CREATE INDEX IX1 ON 
PURCHASING.PURCHASEORDERHEADER (EMPLOYEEID,ORDERDATE) 

Si ahora volvemos a correr la consulta anterior veremos el cambio en nuestro plan de ejecución donde no solo no esta el operador SORT sino que su costo a bajado

Conclusiones finales

Usar un ORDER BY cuando no tenemos un TOP no tiene mucho sentido y genera un consumo mas alto de recursos en nuestro servidor de base de datos, sobre todo CPU (recordemos que por este recurso es como se nos cobra tanto en licencias o azure)

Crear índices para solo ordenar suele ser una muy mala idea, básicamente porque vamos a penalizar las operaciones de insert, update , delete o merge.

Esta práctica del ORDER BY la he visto en mucho código TSQL a lo largo de mis años haciendo optimizaciones, yo recomiendo evitarla por completo y si hay un TOP en ese caso si crear el índice correspondiente

Video explicativo

Links adicionales

SQL Sentry Explore Plan

SQLQueryStress

Video y material Webinar TSQL Tips

Workshop Herramientas SQL Server RML

Las herramientas RML de Microsoft existen desde hace muchos años.

Las mismas son gratuitas y a los DBA o programadores les permiten entre otras cosas poder hacer pruebas de carga (estrés) o también analizar el resultado de un Trace o .XEL de performance.

En el siguiente link de github he creado un workshop en español paso a paso de como usar dos de estas herramientas (ReadTrace y Ostress)

Video y material Webinar TSQL Tips

SQL Server: LEFT JOIN vs NOT EXISTS (Performance)


En mis años como consultor he visto en muchas ocasiones que los programadores usan los JOIN para hacer algún tipo de búsqueda del tipo: Traer los clientes que no tienen ventas o cosas similares.


El JOIN como concepto esta mas pensado para buscar datos entre tablas y presentarlos, por ejemplo si tenemos una tabla facturas y otra clientes entonces es lógico que si necesitamos datos de clientes necesitemos hacer algún tipo de JOIN


En este post veremos el impacto de performance que tiene usar un LEFT JOIN para buscar por ejemplo los clientes que no tienen ordenes en lugar de usar un NOT EXISTS.


Veamos el siguiente ejemplo donde las dos consultas van a retornar los mismos resultados.

USE ADVENTUREWORKS2017 
GO

SELECT * FROM PRODUCTION.PRODUCT P
WHERE NOT EXISTS 
(
 SELECT * FROM SALES.SALESORDERDETAIL D
 WHERE P.PRODUCTID = D.PRODUCTID 
)

SELECT P.* FROM PRODUCTION.PRODUCT P
LEFT JOIN SALES.SALESORDERDETAIL D
ON P.PRODUCTID = D.PRODUCTID 
WHERE D.PRODUCTID IS NULL
En ambas consultas vamos a observar que los resultados son idénticos, retornando 238 registros.

Ahora bien vamos a ver que sucede con sus respectivos planes de ejecución

Podemos observar que en el caso del NOT EXISTS hay una operación eficiente en del índice pero en el LEFT JOIN vemos un SCAN
El costo del primer plan es de 0.10 y el segundo de 0.68 (casi 7 veces mas)

Video y material Webinar TSQL Tips

Como optimizar el uso del operador OR en TSQL

Uno de los grandes problemas que nos podemos encontrar en una consulta TSQL es el uso del operador OR , el mismo es considerado un anti-patron de performance.

En este post veremos como podemos mejorar la performance de una consulta de SQL Server la cual contiene este operador.

Supongamos que tenemos la siguiente consulta:

USE AdventureWorks2017 
GO

SELECT SalesOrderID,
       ProductID, 
       UnitPrice, 
       CarrierTrackingNumber
FROM Sales.SalesOrderDetail 
WHERE ProductID = 709   
OR UnitPrice =  5.7
Si observamos su plan de ejecución nos encontraremos con un Scan sobre el índice clustered que es lo mismo que recorrer toda la tabla.
Ahora vamos a cambiar nuestra consulta usando UNION en lugar del operador OR
SELECT SalesOrderID,
       ProductID, 
       UnitPrice, 
       CarrierTrackingNumber
FROM Sales.SalesOrderDetail 
WHERE ProductID = 709   
UNION
SELECT SalesOrderID,
       ProductID, 
       UnitPrice, 
       CarrierTrackingNumber
FROM Sales.SalesOrderDetail 
WHERE  UnitPrice =  5.7
Si observamos el plan de ejecución veremos que no solo ha cambiado sino que además nos sugiere crear índices
Para mejorar nuestra nueva consulta con el UNION le crearemos los dos siguientes índices
CREATE NONCLUSTERED INDEX [IX_OR]
ON [Sales].[SalesOrderDetail] ([UnitPrice])
INCLUDE ([CarrierTrackingNumber],[ProductID])

CREATE NONCLUSTERED INDEX [IX_OR2]
ON [Sales].[SalesOrderDetail] ([productid])
INCLUDE ([CarrierTrackingNumber],[unitprice])
Ejecutaremos nuestra consulta original con el OR y la nueva con el UNION ya con los índices creados para ver los planes de ejecución y costos
Como se puede observar en los planes de ejecución hay una gran mejora de performance usando el UNION, donde el plan con el OR tiene un costo de “0.63” haciendo un Index Scan vs los “0.023” usando el UNION y con Index Seek.