Tengo una consulta SQL que he pasado los últimos dos días tratando de optimizar usando prueba y error y el plan de ejecución, pero fue en vano. Perdóname por hacer esto, pero publicaré todo el plan de ejecución aquí. He hecho el esfuerzo de hacer que los nombres de tabla y columna en el plan de consulta y ejecución sean genéricos tanto por brevedad como para proteger la propiedad intelectual de mi empresa. El plan de ejecución se puede abrir con SQL Sentry Plan Explorer .
He hecho una buena cantidad de T-SQL, pero el uso de planes de ejecución para optimizar mi consulta es un área nueva para mí y realmente he tratado de entender cómo hacerlo. Por lo tanto, si alguien pudiera ayudarme con esto y explicar cómo se puede descifrar este plan de ejecución para encontrar formas en la consulta para optimizarlo, estaría eternamente agradecido. Tengo muchas más consultas para optimizar, solo necesito un trampolín para ayudarme con este primero.
Esta es la consulta:
DECLARE @Param0 DATETIME = '2013-07-29';
DECLARE @Param1 INT = CONVERT(INT, CONVERT(VARCHAR, @Param0, 112))
DECLARE @Param2 VARCHAR(50) = 'ABC';
DECLARE @Param3 VARCHAR(100) = 'DEF';
DECLARE @Param4 VARCHAR(50) = 'XYZ';
DECLARE @Param5 VARCHAR(100) = NULL;
DECLARE @Param6 VARCHAR(50) = 'Text3';
SET NOCOUNT ON
DECLARE @MyTableVar TABLE
(
B_Var1_PK int,
Job_Var1 varchar(512),
Job_Var2 varchar(50)
)
INSERT INTO @MyTableVar (B_Var1_PK, Job_Var1, Job_Var2)
SELECT B_Var1_PK, Job_Var1, Job_Var2 FROM [fn_GetJobs] (@Param1, @Param2, @Param3, @Param4, @Param6);
CREATE TABLE #TempTable
(
TTVar1_PK INT PRIMARY KEY,
TTVar2_LK VARCHAR(100),
TTVar3_LK VARCHAR(50),
TTVar4_LK INT,
TTVar5 VARCHAR(20)
);
INSERT INTO #TempTable
SELECT DISTINCT
T.T1_PK,
T.T1_Var1_LK,
T.T1_Var2_LK,
MAX(T.T1_Var3_LK),
T.T1_Var4_LK
FROM
MyTable1 T
INNER JOIN feeds.MyTable2 A ON A.T2_Var1 = T.T1_Var4_LK
INNER JOIN @MyTableVar B ON B.Job_Var2 = A.T2_Var2 AND B.Job_Var1 = A.T2_Var3
GROUP BY T.T1_PK, T.T1_Var1_LK, T.T1_Var2_LK, T.T1_Var4_LK
-- This is the slow statement...
SELECT
CASE E.E_Var1_LK
WHEN 'Text1' THEN T.TTVar2_LK + '_' + F.F_Var1
WHEN 'Text2' THEN T.TTVar2_LK + '_' + F.F_Var2
WHEN 'Text3' THEN T.TTVar2_LK
END,
T.TTVar4_LK,
T.TTVar3_LK,
CASE E.E_Var1_LK
WHEN 'Text1' THEN F.F_Var1
WHEN 'Text2' THEN F.F_Var2
WHEN 'Text3' THEN T.TTVar5
END,
A.A_Var3_FK_LK,
C.C_Var1_PK,
SUM(CONVERT(DECIMAL(18,4), A.A_Var1) + CONVERT(DECIMAL(18,4), A.A_Var2))
FROM #TempTable T
INNER JOIN TableA (NOLOCK) A ON A.A_Var4_FK_LK = T.TTVar1_PK
INNER JOIN @MyTableVar B ON B.B_Var1_PK = A.Job
INNER JOIN TableC (NOLOCK) C ON C.C_Var2_PK = A.A_Var5_FK_LK
INNER JOIN TableD (NOLOCK) D ON D.D_Var1_PK = A.A_Var6_FK_LK
INNER JOIN TableE (NOLOCK) E ON E.E_Var1_PK = A.A_Var7_FK_LK
LEFT OUTER JOIN feeds.TableF (NOLOCK) F ON F.F_Var1 = T.TTVar5
WHERE A.A_Var8_FK_LK = @Param1
GROUP BY
CASE E.E_Var1_LK
WHEN 'Text1' THEN T.TTVar2_LK + '_' + F.F_Var1
WHEN 'Text2' THEN T.TTVar2_LK + '_' + F.F_Var2
WHEN 'Text3' THEN T.TTVar2_LK
END,
T.TTVar4_LK,
T.TTVar3_LK,
CASE E.E_Var1_LK
WHEN 'Text1' THEN F.F_Var1
WHEN 'Text2' THEN F.F_Var2
WHEN 'Text3' THEN T.TTVar5
END,
A.A_Var3_FK_LK,
C.C_Var1_PK
IF OBJECT_ID(N'tempdb..#TempTable') IS NOT NULL
BEGIN
DROP TABLE #TempTable
END
IF OBJECT_ID(N'tempdb..#TempTable') IS NOT NULL
BEGIN
DROP TABLE #TempTable
END
Lo que he encontrado es que la tercera declaración (comentada como lenta) es la parte que lleva más tiempo. Las dos declaraciones anteriores vuelven casi al instante.
El plan de ejecución está disponible como XML en este enlace .
Es mejor hacer clic derecho y guardar y luego abrir en SQL Sentry Plan Explorer o algún otro software de visualización en lugar de abrirlo en su navegador.
Si necesita más información sobre las tablas o los datos, no dude en preguntar.
tempdb
. es decir, las estimaciones para las filas resultantes de la unión entreTableA
y@MyTableVar
están muy alejadas. Además, el número de filas que van a los tipos es mucho mayor de lo estimado, por lo que también podrían estar derramándose.Respuestas:
Antes de llegar a la respuesta principal, hay dos piezas de software que necesita actualizar.
Actualizaciones de software requeridas
El primero es SQL Server. Está ejecutando SQL Server 2008 Service Pack 1 (compilación 2531). Debería estar actualizado al menos al Service Pack actual (SQL Server 2008 Service Pack 3 - build 5500). La versión más reciente de SQL Server 2008 en el momento de la escritura es Service Pack 3, Actualización acumulativa 12 (versión 5844).
La segunda pieza de software es SQL Sentry Plan Explorer . Las últimas versiones tienen nuevas características y correcciones significativas, incluida la capacidad de cargar directamente un plan de consulta para análisis experto (¡no es necesario pegar XML en ningún lado!)
Análisis del plan de consulta
La estimación de cardinalidad para la variable de tabla es exactamente correcta, gracias a una recopilación a nivel de declaración:
Desafortunadamente, las variables de tabla no mantienen estadísticas de distribución, por lo que todo lo que sabe el optimizador es que hay seis filas; no sabe nada de los valores que podrían estar en esas seis filas. Esta información es crucial dado que la siguiente operación es unir a otra tabla. La estimación de cardinalidad de esa unión se basa en una suposición descabellada del optimizador:
A partir de ese momento, el plan elegido por el optimizador se basa en información incorrecta, por lo que no es de extrañar que el rendimiento sea tan bajo. En particular, la memoria reservada para géneros y tablas hash para combinaciones hash será demasiado pequeña. En el momento de la ejecución, los desbordamientos y las operaciones de hash se derramarán en el disco físico tempdb.
SQL Server 2008 no destaca esto en los planes de ejecución; puede monitorear los derrames usando Extended Events o Profiler advertencias de clasificación de perfil y advertencias de hash . La memoria está reservada para tipos y hashes basados en estimaciones de cardinalidad antes de que comience la ejecución, y no se puede aumentar durante la ejecución independientemente de la cantidad de memoria libre que pueda tener su SQL Server. Por lo tanto, las estimaciones precisas del recuento de filas son cruciales para cualquier plan de ejecución que implique operaciones que consuman memoria en el espacio de trabajo.
Su consulta también está parametrizada. Deberías considerar agregar
OPTION (RECOMPILE)
a la consulta si diferentes valores de parámetros afectan el plan de consulta. Probablemente debería considerar usarlo de todos modos, para que el optimizador pueda ver el valor@Param1
en el momento de la compilación. Por lo menos, esto puede ayudar al optimizador a producir una estimación más razonable para la búsqueda de índice que se muestra arriba, dado que la tabla es muy grande y está particionada. También puede habilitar la eliminación de particiones estáticas.Intente la consulta nuevamente con una tabla temporal en lugar de la variable de tabla y
OPTION (RECOMPILE)
. También debe intentar materializar el resultado de la primera unión en otra tabla temporal y ejecutar el resto de la consulta en ese sentido. El número de filas no es tan grande (3,285,620), por lo que esto debería ser razonablemente rápido. El optimizador tendrá una estimación de cardinalidad exacta y estadísticas de distribución para el resultado de la unión. Con suerte, el resto del plan encajará bien.Trabajando a partir de las propiedades que se muestran en el plan, la consulta de materialización sería:
También podría
INSERT
ingresar a una tabla temporal predefinida (los tipos de datos correctos no se muestran en el plan, por lo que no puedo hacer esa parte). La nueva tabla temporal puede o no beneficiarse de los índices agrupados y no agrupados.fuente
#AnotherTempTable
. Esto pareció tener el mejor impacto: las otras sugerencias (el uso de una tabla temporal en lugar de una variable de tabla para @MyTableVar y el usoOPTION (RECOMPILE)
no tuvieron mucho efecto o ninguno. El 'Anonimizar' y 'Publicar en SQLPerformance.com' las opciones en SQL Sentry Plan Explorer son geniales: las acabo de usar: answers.sqlperformance.com/questions/1087Noté que debería haber un PK en @MyTableVar y estoy de acuerdo en que #MyTableVar a menudo tiene un mejor rendimiento (especialmente con un mayor número de filas).
La condición dentro de la cláusula where
debe moverse a la unión interna A AND'ed. El optimizador no es lo suficientemente inteligente en mi experiencia para hacer esto (lo siento, no miró el plan) y puede hacer una gran diferencia.
Si esos cambios no muestran una mejora, luego crearía otra tabla temporal de A y todas las cosas a las que se une restringido (¿bien?) Por A.A_Var8_FK_LK = @ Param1 si esa agrupación tiene sentido lógico para usted.
Luego, cree un índice agrupado en esa tabla temporal (antes o después de la creación) para la siguiente condición de unión.
Luego, une ese resultado a las pocas tablas (F y T) que quedan.
Bam, que necesita un plan de consulta apestoso cuando las estimaciones de la fila están apagadas y, a veces, no son fáciles de mejorar ). Sin embargo, supongo que tiene índices adecuados, que es lo que primero verificaría dentro del plan.
Una traza puede mostrar los derrames tempdb que pueden tener o no un impacto drástico.
Otro enfoque alternativo, que es más rápido probar al menos, es ordenar las tablas desde el número más bajo de filas (A) hasta el más alto y luego comenzar a agregar la fusión, el hash y el bucle a las uniones. Cuando hay sugerencias, el orden de unión se fija como se especifica. Otros usuarios evitan sabiamente este enfoque porque a largo plazo puede doler si el recuento relativo de filas cambia drásticamente. Es deseable un número mínimo de pistas.
Si está haciendo muchos de estos, quizás valga la pena probar (o probar) un optimizador comercial y siga siendo una buena experiencia de aprendizaje.
fuente