Actualizar una tabla de manera eficiente usando JOIN

8

Tengo una tabla que contiene los detalles de los hogares y otra que contiene los detalles de todas las personas asociadas a los hogares. Para la tabla doméstica, tengo una clave principal definida usando dos columnas en ella [tempId,n]. Para la tabla de personas, tengo una clave principal definida usando 3 de sus columnas[tempId,n,sporder]

Utilizando la clasificación dictada por la indexación agrupada en las claves primarias, he generado una identificación única para cada hogar [HHID]y cada [PERID]registro de persona (el fragmento a continuación es para generar PERID):

 ALTER TABLE dbo.persons
 ADD PERID INT IDENTITY
 CONSTRAINT [UQ dbo.persons HHID] UNIQUE;

Ahora, mi próximo paso es asociar a cada persona con los hogares correspondientes, es decir; mapear de a [PERID]a a [HHID]. El cruce de peatones entre las dos mesas se basa en las dos columnas [tempId,n]. Para esto tengo la siguiente declaración de unión interna.

UPDATE t1
  SET t1.HHID = t2.HHID
  FROM dbo.persons AS t1
  INNER JOIN dbo.households AS t2
  ON t1.tempId = t2.tempId AND t1.n = t2.n;

Tengo un total de 1928783 registros de hogares y 5239842 registros de personas. El tiempo de ejecución es actualmente muy alto.

Ahora, mis preguntas:

  1. ¿Es posible optimizar aún más esta consulta? En términos más generales, ¿cuáles son las reglas básicas para optimizar una consulta de unión?
  2. ¿Hay otra construcción de consulta que pueda lograr el resultado que quiero con un mejor tiempo de ejecución?

He cargado el plan de ejecución generado por SQL Server 2008 para todo el script en SQLPerformance.com

sriramn
fuente

Respuestas:

19

Estoy bastante seguro de que las definiciones de tabla están cerca de esto:

CREATE TABLE dbo.households
(
    tempId  integer NOT NULL,
    n       integer NOT NULL,
    HHID    integer IDENTITY NOT NULL,

    CONSTRAINT [UQ dbo.households HHID] 
        UNIQUE NONCLUSTERED (HHID),

    CONSTRAINT [PK dbo.households tempId, n]
    PRIMARY KEY CLUSTERED (tempId, n)
);

CREATE TABLE dbo.persons
(
    tempId  integer NOT NULL,
    sporder integer NOT NULL,
    n       integer NOT NULL,
    PERID   integer IDENTITY NOT NULL,
    HHID    integer NOT NULL,

    CONSTRAINT [UQ dbo.persons HHID]
        UNIQUE NONCLUSTERED (PERID),

    CONSTRAINT [PK dbo.persons tempId, n, sporder]
        PRIMARY KEY CLUSTERED (tempId, n, sporder)
);

No tengo estadísticas para estas tablas o sus datos, pero lo siguiente al menos establecerá la cardinalidad de la tabla correcta (los recuentos de páginas son una suposición):

UPDATE STATISTICS dbo.persons 
WITH 
    ROWCOUNT = 5239842, 
    PAGECOUNT = 100000;

UPDATE STATISTICS dbo.households 
WITH 
    ROWCOUNT = 1928783, 
    PAGECOUNT = 25000;

Análisis del plan de consulta

La consulta que tiene ahora es:

UPDATE P
SET HHID = H.HHID
FROM dbo.households AS H
JOIN dbo.persons AS P
    ON P.tempId = H.tempId
    AND P.n = H.n;

Esto genera el plan bastante ineficiente:

Plan predeterminado

Los principales problemas en este plan son el hash join y sort. Ambos requieren una concesión de memoria (la combinación hash necesita construir una tabla hash, y la ordenación necesita espacio para almacenar las filas mientras la ordenación progresa). Plan Explorer muestra que esta consulta recibió 765 MB:

Concesión de memoria

¡Esto es bastante memoria del servidor para dedicar a una consulta! Más concretamente, esta concesión de memoria se repara antes de que comience la ejecución en función del recuento de filas y las estimaciones de tamaño.

Si la memoria resulta insuficiente en el momento de la ejecución, al menos algunos datos para el hash y / o la clasificación se escribirán en el disco físico tempdb . Esto se conoce como 'derrame' y puede ser una operación muy lenta. Puede rastrear estos derrames (en SQL Server 2008) utilizando los eventos Profiler Hash Warnings y Sort Warnings .

La estimación de la entrada de compilación de la tabla hash es muy buena:

Entrada de hash

La estimación para la entrada de clasificación es menos precisa:

Ordenar entrada

Tendría que usar Profiler para verificar, pero sospecho que el tipo se derramará a tempdb en este caso. También es posible que la tabla hash se derrame también, pero eso es menos claro.

Tenga en cuenta que la memoria reservada para esta consulta se divide entre la tabla hash y la clasificación, porque se ejecutan simultáneamente. La propiedad del plan Fracciones de memoria muestra la cantidad relativa de la concesión de memoria que se espera que utilice cada operación.

¿Por qué ordenar y hacer picadillo?

El optimizador de consultas introduce la clasificación para garantizar que las filas lleguen al operador de Actualización de índice agrupado en orden de clave agrupado. Esto promueve el acceso secuencial a la tabla, que a menudo es mucho más eficiente que el acceso aleatorio.

La combinación hash es una opción menos obvia, porque sus entradas son de tamaños similares (de todos modos, para una primera aproximación). La combinación de hash es mejor cuando una entrada (la que construye la tabla hash) es relativamente pequeña.

En este caso, el modelo de costos del optimizador determina que la combinación hash es la más barata de las tres opciones (hash, merge, loops anidados).

Mejorando el desempeño

El modelo de costos no siempre lo hace bien. Tiende a sobreestimar el costo de la combinación de combinación paralela, especialmente a medida que aumenta el número de subprocesos. Podemos forzar una combinación de combinación con una sugerencia de consulta:

UPDATE P
SET HHID = H.HHID
FROM dbo.households AS H
JOIN dbo.persons AS P
    ON P.tempId = H.tempId
    AND P.n = H.n
OPTION (MERGE JOIN);

Esto produce un plan que no requiere tanta memoria (porque la combinación de combinación no necesita una tabla hash):

Plan de fusión

El tipo problemático sigue ahí, porque la combinación de combinación solo conserva el orden de sus claves de combinación (tempId, n), pero las claves agrupadas son (tempId, n, sporder). Es posible que el plan de combinación de combinación no funcione mejor que el plan de combinación hash.

Los bucles anidados se unen

También podemos probar una unión de bucles anidados:

UPDATE P
SET HHID = H.HHID
FROM dbo.households AS H
JOIN dbo.persons AS P
    ON P.tempId = H.tempId
    AND P.n = H.n
OPTION (LOOP JOIN);

El plan para esta consulta es:

Plan de bucles anidados en serie

Este plan de consulta es considerado el peor por el modelo de costos del optimizador, pero tiene algunas características muy deseables. Primero, la unión de bucles anidados no requiere una concesión de memoria. En segundo lugar, puede preservar el orden de las claves de la Personstabla para que no se necesite una ordenación explícita. Es posible que este plan funcione relativamente bien, tal vez incluso lo suficientemente bueno.

Bucles anidados paralelos

El gran inconveniente con el plan de bucles anidados es que se ejecuta en un solo hilo. Es probable que esta consulta se beneficie del paralelismo, pero el optimizador decide que no hay ninguna ventaja en hacerlo aquí. Esto tampoco es necesariamente correcto. Desafortunadamente, no hay una sugerencia de consulta integrada para obtener un plan paralelo, pero hay una forma no documentada:

UPDATE t1
  SET t1.HHID = t2.HHID
  FROM dbo.persons AS t1
  INNER JOIN dbo.households AS t2
  ON t1.tempId = t2.tempId AND t1.n = t2.n
OPTION (LOOP JOIN, QUERYTRACEON 8649);

Habilitar el indicador de seguimiento 8649 con la QUERYTRACEONsugerencia produce este plan:

Plan de bucles anidados paralelos

Ahora tenemos un plan que evita la clasificación, no requiere memoria adicional para la unión y utiliza el paralelismo de manera efectiva. Debería encontrar que esta consulta funciona mucho mejor que las alternativas.

Más información sobre paralelismo en mi artículo Forzar un plan de ejecución de consultas paralelas :

Paul White 9
fuente
1

Al observar su plan de consultas, es posible que su problema real no sea la unión per se sino el proceso real de actualización.

Por lo que puedo ver, es probable que esté actualizando todos los registros de personas en su base de datos y actualizando índices (no puedo ver qué otros índices tiene esto, así que no sé si eso podría ser un factor)

Si esta es una tarea única, ¿podría deshabilitar los índices, ejecutar la actualización y reconstruir los índices?

Una vez que haya completado los datos, puede agregar una cláusula where a su consulta para actualizar solo aquellos registros que necesitan actualizarse.

TomGough
fuente