Superar MERGE JOIN (INDEX SCAN) con un valor explícito de CLAVE en una CLAVE EXTRANJERA

9

Agregado 7/11 El problema es que los puntos muertos ocurren debido a la exploración del índice durante MERGE JOIN. En este caso, una transacción que intenta obtener el bloqueo S en todo el índice en la tabla primaria FK, pero anteriormente otra transacción coloca el bloqueo X en un valor clave del índice.

Permítanme comenzar con un pequeño ejemplo (se usa TSQL2012 DB de 70-461 cource):

CREATE TABLE [Sales].[Orders](
[orderid] [int] IDENTITY(1,1) NOT NULL,
[custid] [int] NULL,
[empid] [int] NOT NULL,
[shipperid] [int] NOT NULL,
... )

Las columnas [custid], [empid], [shipperid]son parámetros correlacionados para ello [Sales].[Customers], [HR].[Employees], [Sales].[Shippers]. En cada caso tenemos un índice agrupado en una columna referida en una tabla parental.

ALTER TABLE [Sales].[Orders]  WITH CHECK ADD  CONSTRAINT [FK_Orders_Customers] FOREIGN KEY([custid]) REFERENCES [Sales].[Customers] ([custid])
ALTER TABLE [Sales].[Orders]  WITH CHECK ADD  CONSTRAINT [FK_Orders_Employees] FOREIGN KEY([empid]) REFERENCES [HR].[Employees] ([empid])
ALTER TABLE [Sales].[Orders]  WITH CHECK ADD  CONSTRAINT [FK_Orders_Shippers] FOREIGN KEY([shipperid])REFERENCES [Sales].[Shippers] ([shipperid])

Estoy intentando con INSERT [Sales].[Orders] SELECT ... FROMotra tabla llamada [Sales].[OrdersCache]que tiene la misma estructura que las [Sales].[Orders]claves externas excepto. Otra cosa que podría ser importante mencionar es que la tabla [Sales].[OrdersCache]es un índice agrupado.

CREATE CLUSTERED INDEX idx_c_OrdersCache ON Sales.OrdersCache ( custid, empid )

Como era de esperar cuando estoy tratando de insertar un volumen bajo de datos, LOOP JOIN funciona bien haciendo que la búsqueda de índice en las claves foráneas.

Con grandes volúmenes de datos, el optimizador de consultas utiliza MERGE JOIN como la forma más eficiente de mantener la clave foregn en la consulta.

Y no hay nada que ver con él, excepto usar OPTION (LOOP JOIN) en nuestro caso con claves foráneas o INNER LOOP JOIN en el caso explícito de JOIN.

A continuación se muestra la consulta que estoy tratando de ejecutar en mi entorno:

INSERT Sales.Orders (
        custid, empid, shipperid, ... )
SELECT  custid, empid, 2, ...
FROM Sales.OrdersCache

Mirando el plan podemos ver que las 3 claves extranjeras validadas con MERGE JOIN. No es una forma apropiada para mí, ya que utiliza INDEX SCAN con bloqueo de índice completo. MERGE JOIN durante la validación de claves foráneas

Usar OPTION (LOOP JOIN) no es adecuado ya que cuesta casi un 15% más que MERGE JOIN (creo que la regresión será mayor con el crecimiento de los volúmenes de datos).

En la instrucción SELECT puede ver un valor único para el shipperidatributo para todo el conjunto insertado. En mi opinión, debe haber una manera de hacer que la fase de validación para el conjunto insertado sea más rápida al menos para el atributo inmutable. Algo como:

  • hacer LOOP JOIN, MERGE JOIN, HASH JOIN si tenemos un subconjunto indefinido para la validación de JOIN
  • si solo hay un valor explícito de la columna validada, hacemos la validación solo una vez (INDEX SEEK).

¿Hay algún patrón común para superar la situación anterior utilizando estructuras de código, objetos DDL adicionales, etc.?

Añadido 20/07. Solución. Query Optimizer ya realiza una optimización de validación de 'clave única: clave externa' mediante MERGE JOIN. Y se dirige solo a la tabla Sales.Shippers, dejando LOOP JOIN para otras uniones en la consulta al mismo tiempo. Como tengo algunas filas en la tabla principal, el Optimizador de consultas utiliza el algoritmo de combinación Ordenar-fusionar y comparar cada fila de la tabla interna con la tabla principal solo una vez. Entonces esa es la respuesta a mi pregunta si hay algún mecanismo particular para procesar efectivamente valores individuales en un conjunto durante la validación de una sola clave. Esa no es una decisión tan perfecta, pero esa es la forma en que SQL Server optimiza el caso.

La investigación sobre el rendimiento del rendimiento reveló que en mi caso la instrucción de inserción MERGE JOIN y LOOP JOIN se volvió aproximadamente igual a 750 filas insertadas simultáneamente con la siguiente superioridad de MERGE JOIN (en el recurso de tiempo de CPU). Por lo tanto, usar OPTION (LOOP JOIN) es una solución adecuada para mi proceso comercial.

Oleg I
fuente

Respuestas:

8

Usar OPTION (LOOP JOIN) no es adecuado ya que cuesta casi un 15% más que MERGE JOIN

Los porcentajes de costos que se muestran en la salida del plan de presentación son siempre estimaciones del modelo optimizador, incluso en un plan posterior a la ejecución (real). Es probable que estos costos no reflejen el rendimiento real del tiempo de ejecución en su hardware en particular. La única forma de estar seguro es probar las alternativas con su carga de trabajo, midiendo las métricas que sean más importantes para usted (tiempo transcurrido, uso de la CPU, etc.).

En mi experiencia, el optimizador cambia de unión de bucles a combinación de unión para validación de clave externa demasiado pronto. En todas las operaciones, excepto en las más grandes, he descubierto que es preferible unir bucles. Y por "grande" me refiero a decenas de millones de filas al menos, ciertamente no a las miles que indicas en los comentarios a tu pregunta.

En mi opinión, debe haber una manera de hacer que la fase de validación para el conjunto insertado sea más rápida al menos para el atributo inmutable.

Esto tiene sentido en principio, pero no existe tal lógica hoy dentro de la lógica de construcción de planes que utiliza la validación de clave externa. La lógica actual es deliberadamente muy genérica y ortogonal a la consulta más amplia; Las optimizaciones específicas complican las pruebas y aumentan la probabilidad de errores extremos.

¿Hay algún patrón común para superar la situación anterior utilizando estructuras de código, objetos DDL adicionales, etc.?

No que yo sepa. El riesgo de punto muerto con la combinación de validación de clave externa es bien conocido , siendo la solución la solución más utilizada OPTION (LOOP JOIN). No hay forma de evitar que se tomen bloqueos compartidos durante la validación de clave externa, ya que estos son necesarios para la corrección , incluso bajo niveles de aislamiento de versiones de fila.

No hay una mejor respuesta general (que la sugerencia de unión de bucle) si desea conservar la capacidad de múltiples procesos concurrentes para agregar filas a las tablas padre e hijo transaccionalmente. Pero, si está dispuesto a serializar las modificaciones de datos (no las lecturas), usar sp_getapplock es una técnica confiable y simple.

Paul White 9
fuente