sql >> Base de Datos >  >> RDS >> Sqlserver

Internos de SQL Server:Operadores problemáticos Pt. II – Hashing

Esto es parte de una serie de operadores problemáticos internos de SQL Server. Para leer la primera publicación, haga clic aquí.

SQL Server existe desde hace más de 30 años y yo he estado trabajando con SQL Server durante casi el mismo tiempo. He visto muchos cambios a lo largo de los años (¡y décadas!) y versiones de este increíble producto. En estas publicaciones, compartiré con ustedes cómo veo algunas de las características o aspectos de SQL Server, a veces junto con un poco de perspectiva histórica.

La última vez, hablé sobre una operación de escaneo en un plan de consulta de SQL Server como un operador potencialmente problemático en los diagnósticos de SQL Server. Aunque los escaneos con frecuencia se usan solo porque no hay un índice útil, hay momentos en que el escaneo es en realidad una mejor opción que una operación de búsqueda de índice.

En este artículo, les contaré sobre otra familia de operadores que ocasionalmente se ve como problemática:el hashing. Hashing es un algoritmo de procesamiento de datos muy conocido que existe desde hace muchas décadas. Lo estudié en mis clases de estructuras de datos cuando estaba estudiando informática en la Universidad. Si desea obtener información general sobre el hash y las funciones hash, puede consultar este artículo en Wikipedia. Sin embargo, SQL Server no agregó hashing a su repertorio de opciones de procesamiento de consultas hasta SQL Server 7. (Aparte, mencionaré que SQL Server sí usó hash en algunos de sus propios algoritmos de búsqueda internos. Como menciona el artículo de Wikipedia , hash usa una función especial para mapear datos de tamaño arbitrario a datos de un tamaño fijo. SQL usó hash como técnica de búsqueda para mapear cada página de una base de datos de tamaño arbitrario a un búfer en la memoria, que es un tamaño fijo. De hecho , solía haber una opción para sp_configure llamados "cubos de hash", que le permitían controlar la cantidad de cubos utilizados para el hash de las páginas de la base de datos en los búferes de memoria).

¿Qué es Hashing?

Hashing es una técnica de búsqueda que no requiere ordenar los datos. SQL Server puede usarlo para operaciones JOIN, operaciones de agregación (DISTINCT o GROUP BY) u operaciones UNION. Lo que estas tres operaciones tienen en común es que, durante la ejecución, el motor de consultas busca valores coincidentes. En un JOIN, queremos encontrar filas en una tabla (o conjunto de filas) que tengan valores coincidentes con filas en otra. (Y sí, conozco uniones que no comparan filas en función de la igualdad, pero esas uniones no equitativas son irrelevantes para esta discusión). Para GROUP BY, encontramos valores coincidentes para incluir en el mismo grupo, y para UNION y DISTINCT, buscamos valores coincidentes para excluirlos. (Sí, sé que UNION ALL es una excepción).

Antes de SQL Server 7, la única forma en que estas operaciones podían encontrar fácilmente valores coincidentes era si los datos estaban ordenados. Por lo tanto, si no hubiera un índice existente que mantuviera los datos ordenados, el plan de consulta agregaría una operación SORT al plan. Hashing organiza sus datos para una búsqueda eficiente al colocar todas las filas que tienen el mismo resultado de la función hash interna en el mismo "cubo de hash".

Para obtener una explicación más detallada de la operación hash JOIN de SQL Server, incluidos los diagramas, consulte esta publicación de blog de SQL Shack.

Una vez que el hashing se convirtió en una opción, SQL Server no descartó por completo la posibilidad de ordenar los datos antes de unirlos o agregarlos, pero simplemente se convirtió en una posibilidad para que el optimizador la considerara. Sin embargo, en general, si intenta unir, agregar o realizar UNION en datos no ordenados, el optimizador generalmente elegirá una operación hash. Mucha gente asume que HASH JOIN (u otra operación HASH) en un plan significa que no tiene índices apropiados y que debe crear índices apropiados para evitar la operación hash.

Veamos un ejemplo. Primero crearé dos tablas sin indexar.

USE AdventureWorks2016 GO DROP TABLE IF EXISTS Details;

GO

SELECT * INTO Details FROM Sales.SalesOrderDetail;

GO

DROP TABLE IF EXISTS Headers;

GO

SELECT * INTO Headers FROM Sales.SalesOrderHeader;

GO

Now, I’ll join these two tables together and filter the rows in the Details table:

SELECT *

FROM Details d JOIN Headers h

ON d.SalesOrderID = h.SalesOrderID

WHERE SalesOrderDetailID < 100;

Quest Spotlight Tuning Pack no parece indicar que la unión hash sea un problema. Solo resalta los dos escaneos de la tabla.

Las sugerencias recomiendan crear un índice en cada tabla que incluya cada columna no clave como una columna INCLUIDA. Raramente tomo esas recomendaciones (como mencioné en mi publicación anterior). Construiré solo el índice en los Detalles tabla, en la columna de unión, y no tiene ninguna columna incluida.

CREATE INDEX Header_index on Headers(SalesOrderID);

Una vez que se crea ese índice, HASH JOIN desaparece. El índice ordena los datos en los Encabezados table y permite que SQL Server encuentre las filas coincidentes en la tabla interna usando la secuencia de clasificación del índice. Ahora, la parte más costosa del plan es el escaneo en la mesa exterior (Detalles ) que podría reducirse creando un índice en el SalesOrderID columna de esa tabla. Lo dejaré como ejercicio para el lector.

Sin embargo, un plan con HASH JOIN no siempre es algo malo. El operador alternativo (excepto en casos especiales) es NESTED LOOPS JOIN, y esa suele ser la elección cuando hay buenos índices presentes. Sin embargo, una operación de bucles NESTED requiere múltiples búsquedas en la tabla interna. El siguiente pseudocódigo muestra el algoritmo de unión de bucles anidados:

for each row R1 in the outer table

     for each row R2 in the inner table

         if R1 joins with R2

             return (R1, R2)

Como su nombre lo indica, una UNIÓN DE BUCLE NESTED se realiza como un bucle anidado. La búsqueda de la tabla interna generalmente se realizará varias veces, una para cada fila calificada en la tabla externa. Incluso si hay solo un pequeño porcentaje de las filas que califican, si la tabla es muy grande (quizás en cientos de millones, o miles de millones o filas), habrá muchas filas para leer. En un sistema que está limitado por E/S, estos millones o miles de millones de lecturas pueden ser un verdadero cuello de botella.

A HASH JOIN, por otro lado, no realiza lecturas múltiples de ninguna de las tablas. Lee la tabla externa una vez para crear los cubos de hash y luego lee la tabla interna una vez, verificando los cubos de hash para ver si hay una fila coincidente. Tenemos un límite superior de un solo paso a través de cada mesa. Sí, se necesitan recursos de CPU para calcular la función hash y administrar el contenido de los depósitos. Hay recursos de memoria necesarios para almacenar la información hash. Pero, si tiene un sistema vinculado a E/S, es posible que tenga memoria y recursos de CPU de sobra. HASH JOIN puede ser una opción razonable para el optimizador en estas situaciones en las que sus recursos de E/S son limitados y está uniendo tablas muy grandes.

Aquí está el pseudocódigo para el algoritmo hash join:

for each row R1 in the build table

  begin

     calculate hash value on R1 join key(s)

     insert R1 into the appropriate hash bucket

  end

for each row R2 in the probe table

  begin

     calculate hash value on R2 join key(s)

     for each row R1 in the corresponding hash bucket

         if R1 joins with R2

         output (R1, R2)

  end

Como se mencionó anteriormente, el hashing también se puede usar para operaciones de agregación (así como UNION). Nuevamente, si hay un índice útil que ya tiene los datos ordenados, la agrupación de datos se puede hacer de manera muy eficiente. Sin embargo, también hay muchas situaciones en las que hash no es un mal operador en absoluto. Considere una consulta como la siguiente, que agrupa los datos en Detalles tabla (creada arriba) por el ProductID columna. Hay 121 317 filas en la tabla y solo 266 ProductID diferentes valores.

SELECT ProductID, count(*)

FROM Details

GROUP BY ProductID;

GO

Uso de operaciones hash

Para usar hashing, SQL Server solo tiene que crear y mantener 266 cubos, que no es mucho. De hecho, Quest Spotlight Tuning Pack no indica que haya ningún problema con esta consulta.

Sí, tiene que hacer un escaneo de la tabla, pero eso se debe a que necesitamos examinar cada fila de la tabla y sabemos que los escaneos no siempre son algo malo. Un índice solo ayudaría con la clasificación previa de los datos, pero el uso de la agregación de hash para un número tan pequeño de grupos seguirá brindando un rendimiento razonable incluso sin un índice útil disponible.

Al igual que los escaneos de tablas, las operaciones hash se consideran con frecuencia como un operador "malo" para tener en un plan. Hay casos en los que puede mejorar en gran medida el rendimiento agregando índices útiles para eliminar las operaciones hash, pero eso no siempre es cierto. Y si está tratando de limitar la cantidad de índices en tablas que están muy actualizadas, debe tener en cuenta que las operaciones de hash no siempre son algo que deba ser 'arreglado', por lo que dejar la consulta para usar un hash puede ser algo razonable. que hacer. Además, para ciertas consultas en tablas grandes que se ejecutan en sistemas vinculados a E/S, el hash puede brindar un mejor rendimiento que los algoritmos alternativos debido a la cantidad limitada de lecturas que se deben realizar. La única forma de saberlo con certeza es probar varias posibilidades en su sistema, con sus consultas y sus datos.

En la siguiente publicación de esta serie, le informaré sobre otros operadores problemáticos que podrían aparecer en sus planes de consulta, ¡así que vuelva pronto!