2009-05-28 27 views
10

Tengo una consulta que se parece a¿Por qué es más rápido insertar y unir tablas #temp?

SELECT 
P.Column1, 
P.Column2, 
P.Column3, 
... 
(
    SELECT 
     A.ColumnX, 
     A.ColumnY, 
     ... 
    FROM 
     dbo.TableReturningFunc1(@StaticParam1, @StaticParam2) AS A 
    WHERE 
     A.Key = P.Key 
    FOR XML AUTO, TYPE 
), 
(
    SELECT 
     B.ColumnX, 
     B.ColumnY, 
     ... 
    FROM 
     dbo.TableReturningFunc2(@StaticParam1, @StaticParam2) AS B 
    WHERE 
     B.Key = P.Key 
    FOR XML AUTO, TYPE 
) 
FROM 
(
    <joined tables here> 
) AS P 
FOR XML AUTO,ROOT('ROOT') 

P ~ tiene 5000 filas A y B ~ 4000 filas cada

Esta consulta tiene un rendimiento de ejecución de ~ 10 + minutos.

Si lo cambia a esto, sin embargo:

SELECT 
P.Column1, 
P.Column2, 
P.Column3, 
... 
INTO #P 

SELECT 
A.ColumnX, 
A.ColumnY, 
... 
INTO #A  
FROM 
dbo.TableReturningFunc1(@StaticParam1, @StaticParam2) AS A 

SELECT 
B.ColumnX, 
B.ColumnY, 
... 
INTO #B  
FROM 
dbo.TableReturningFunc2(@StaticParam1, @StaticParam2) AS B 


SELECT 
P.Column1, 
P.Column2, 
P.Column3, 
... 
(
    SELECT 
     A.ColumnX, 
     A.ColumnY, 
     ... 
    FROM 
     #A AS A 
    WHERE 
     A.Key = P.Key 
    FOR XML AUTO, TYPE 
), 
(
    SELECT 
     B.ColumnX, 
     B.ColumnY, 
     ... 
    FROM 
     #B AS B 
    WHERE 
     B.Key = P.Key 
    FOR XML AUTO, TYPE 
) 
FROM #P AS P 
FOR XML AUTO,ROOT('ROOT')  

tiene un rendimiento de ~ 4 segundos.

Esto no tiene mucho sentido, ya que parece que el costo de insertar en una tabla temporal y luego hacer la unión debería ser mayor de forma predeterminada. Mi inclinación es que SQL está haciendo un tipo incorrecto de "unirse" a la subconsulta, pero tal vez me lo he perdido, no hay forma de especificar el tipo de combinación para usar con las subconsultas correlacionadas.

¿Hay alguna manera de lograr esto sin utilizar las tablas #temp/@ table variables mediante índices y/o sugerencias?

EDITAR: Tenga en cuenta que dbo.TableReturningFunc1 y dbo.TableReturningFunc2 son TVF en línea, no multi declaraciones, o son declaraciones de vista "parametrizadas".

Respuesta

15

Sus procedimientos están siendo reevaluados para cada fila en P.

Lo que usted hace con las tablas temporales es, de hecho, el almacenamiento en caché del conjunto de resultados generados por los procedimientos almacenados, eliminando así la necesidad de reevaluar.

La inserción en una tabla temporal es rápida porque no genera redo/rollback.

se une también son rápidos, ya que el tener un conjunto de resultados estable permite la posibilidad de crear un índice temporal con un Eager Spool o una Worktable

Puede volver a utilizar los procedimientos sin tablas temporales, utilizando CTE 's, pero para que esto sea eficiente, SQL Server necesita materializar los resultados de CTE.

Es posible tratar para forzarlo hacer esto con el uso de un ORDER BY dentro de una subconsulta:

WITH f1 AS 
     (
     SELECT TOP 1000000000 
       A.ColumnX, 
       A.ColumnY 
     FROM dbo.TableReturningFunc1(@StaticParam1, @StaticParam2) AS A 
     ORDER BY 
       A.key 
     ), 
     f2 AS 
     (
     SELECT TOP 1000000000 
       B.ColumnX, 
       B.ColumnY, 
     FROM dbo.TableReturningFunc2(@StaticParam1, @StaticParam2) AS B 
     ORDER BY 
       B.Key 
     ) 
SELECT … 

, lo que puede resultar en Eager Spool generada por el optimizador.

Sin embargo, esto está lejos de ser garantizado.

La manera garantizada es agregar un OPTION (USE PLAN) a su consulta y envolver el corresponda CTE en la cláusula Spool.

Ver esta entrada en mi blog en cómo hacerlo:

Esto es difícil de mantener, ya que tendrá que volver a escribir su plan cada vez que vuelva a escribir la consulta, pero esto funciona bien y es bastante eficiente.

Sin embargo, usar las tablas temporales será mucho más fácil.

+0

¿Cómo podría lograr esto sin hacer la tabla temporal de trabajo explícitamente, si es posible? –

+0

Claro que es posible, consulte la actualización de la publicación – Quassnoi

+0

Rendimiento con CTE 1:28, método de la tabla Temp 0:04. Mucho mejor que 10+ minutos ... pero aún órdenes de magnitud en la diferencia. –

2

Es un problema con su subconsulta que hace referencia a su consulta externa, lo que significa que la consulta secundaria debe compilarse y ejecutarse para en cada fila en la consulta externa. En lugar de utilizar tablas temporales explícitas, puede usar una tabla derivada. Para simplificar su ejemplo:

SELECT P.Column1, 
     (SELECT [your XML transformation etc] FROM A where A.ID = P.ID) AS A 

Si P contiene 10.000 registros luego SELECT A.ColumnX DE A donde se ejecutará A.ID = P.ID 10.000 veces.
en su lugar puede usar una tabla derivada como sigue:

SELECT P.Column1, A2.Column FROM 
P LEFT JOIN 
(SELECT A.ID, [your XML transformation etc] FROM A) AS A2 
ON P.ID = A2.ID 

Está bien, no es tan ilustrativa pseudo-código, pero la idea básica es la misma que la tabla temporal, excepto que SQL Server realiza todo el asunto en la memoria : Primero selecciona todos los datos en "A2" y construye una tabla temporal en la memoria, luego se une a ella. Esto le ahorra tener que seleccionarlo para TEMP usted mismo.

Solo para darle un ejemplo del principio en otro contexto donde pueda tener más sentido inmediato. Considere la información de empleados y ausencias donde desea mostrar el número de días de ausencia registrados para cada empleado.

malo: (carreras como muchos queryes ya que hay empleados en el DB)

SELECT EmpName, 
(SELECT SUM(absdays) FROM Absence where Absence.PerID = Employee.PerID) AS Abstotal   
FROM Employee 

bueno: (sólo se ejecuta dos consultas)

SELECT EmpName, AbsSummary.Abstotal 
FROM Employee LEFT JOIN 
     (SELECT PerID, SUM(absdays) As Abstotal 
     FROM Absence GROUP BY PerID) AS AbsSummary 
ON AbsSummary.PerID = Employee.PerID 
+0

Esto no soluciona completamente el problema que estoy teniendo. Utilizando LEFT OUTER JOINS y convirtiéndolo para usar FOR XML PATH (para obtener una anidación adecuada) obtengo un tiempo de ejecución de 1:26. Comparado con 0:04 para el método de tabla temporal. –

+0

Ah, es suficiente :) – Frans

+0

Bien, esto es lo que haría en este momento; Divida su consulta en sus componentes y cargue cada parte en el Analizador de consultas y seleccione "mostrar el plan de ejecución estimado". Luego haz lo mismo para toda la consulta. Debería darle una buena idea sobre qué paso es lento y cómo el optimizador está interpretando su solicitud. Vea si es uno de los subcomponentes o solo cuando está todo junto. Busque bucles y escaneos. Lo más importante es que probablemente encontrará un paso que es responsable de casi todo el tiempo de ejecución; vea si puede optimizarlo. – Frans

0

Esto no tiene mucho sentido, ya que parecería el costo para insertar en una mesa temperatura y luego hacer la unión debe ser mayor por de> Esto no tiene mucho sentido, ya que parece ser el costo de insertar en una tabla de temperatura y luego hacer la unión debería ser más alto de forma predeterminada.

Con tablas temporales, le indica explícitamente al servidor Sql qué almacenamiento intermedio usar. Pero si oculta todo en una consulta grande, SQL Server decidirá por sí mismo. La diferencia no es tan grande; al final del día, se usa el almacenamiento temporal, ya sea que lo especifique como tabla temporal o no.

En su caso, las tablas temporales funcionan más rápido, entonces ¿por qué no se adhieren a ellas?

1

Existen varias razones posibles por las que el uso de tablas temporales intermedias puede acelerar una consulta, pero lo más probable es que las funciones que se invocan (pero no se enumeran) sean probablemente TVF de varias declaraciones y no TVF en línea. Los TVF de varias declaraciones son opacos para la optimización de sus consultas de llamadas y, por lo tanto, el optimizador no puede decir si hay oportunidades para la reutilización de datos u otras optimizaciones de reordenamiento lógico/físico del operador. Por lo tanto, todo lo que puede hacer es volver a ejecutar los TVF cada vez que se supone que la consulta que contiene generará otra fila con las columnas XML.

En resumen, los TVF de sentencias múltiples frustran el optimizador.

Las soluciones habituales, en orden de preferencia (típico) son:

  1. volver a escribir el infractor instrucción múltiple TVF ser un filtro en línea TVF
  2. en línea el código de función en el consulta de llamada, o
  3. Vuelca los datos del TVF ofensivo en una tabla temporal. que es lo que has hecho ...
+0

Estoy de acuerdo, excepto que los TVF están en línea, básicamente vistas paramaterizadas. Agregará esa aclaración a la pregunta. Buena sugerencia en general sin embargo. –

+1

+1 para el consejo general de que los TVF multi-declaración son opacos para el optimizador. La estimación de cardinalidad es un problema particular. En general, se debe evitar cualquier función con 'BEGIN ... END' en la definición :) –

-2

Si las tablas temporales resultan ser más rápidas en tu caso particular, deberías usar una variable de tabla.

Hay un buen artículo aquí en las diferencias y las implicaciones de rendimiento:

http://www.codeproject.com/KB/database/SQP_performance.aspx

+2

En SQL 2005 y las tablas temporales, las tablas son tan rápidas o rápidas que las variables de tabla la gran mayoría de las veces. Esto se debe a que las variables de tabla no pueden contener estadísticas, por lo que para el optimizador de consultas parece que solo tienen 1 fila (consulte el plan de consulta). Como tales, están optimizados de forma incorrecta y tienden a tener un rendimiento deficiente cada vez que tienen significativamente más de, por ejemplo, 10 filas. Este artículo utiliza pruebas similares al artículo al que hace referencia, pero muestra lo que sucede en 2005 cuando coloca más filas. – RBarryYoung

+0

Vaya, aquí está el artículo: http://www.sql-server-performance.com/articles/ por/temp_tables_vs_variables_p1.aspx – RBarryYoung

+0

Heh. ¡En realidad, ambos son el mismo artículo! – RBarryYoung

4

Esta respuesta debe ser leído junto con el artículo de Quassnoi
http://explainextended.com/2009/05/28/generating-xml-in-subqueries/

Con la aplicación juiciosa de CROSS APPLY, puede forzar el almacenamiento en caché o la evaluación de acceso directo de los TVF en línea. Esta consulta regresa instantáneamente.

SELECT * 
FROM (
     SELECT (
       SELECT f.num 
       FOR XML PATH('fo'), ELEMENTS ABSENT 
       ) AS x 
     FROM [20090528_tvf].t_integer i 
     cross apply (
      select num 
      from [20090528_tvf].fn_num(9990) f 
      where f.num = i.num 
      ) f 
) q 
--WHERE x IS NOT NULL -- covered by using CROSS apply 
FOR XML AUTO 

No ha proporcionado estructuras reales así que es difícil de construir algo significativo, pero la técnica debe aplicarse también.

Si cambia el multi-statement TVF en el artículo de Quassnoi a un TVF en línea, el plan se vuelve aún más rápido (al menos un orden de magnitud) y el plan se reduce mágicamente a algo que no puedo entender (¡es demasiado simple!).

CREATE FUNCTION [20090528_tvf].fn_num(@maxval INT) 
RETURNS TABLE 
AS RETURN 
     SELECT num + @maxval num 
     FROM t_integer 

Estadísticas

SQL Server parse and compile time: 
    CPU time = 0 ms, elapsed time = 0 ms. 

(10 row(s) affected) 
Table 't_integer'. Scan count 2, logical reads 22, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. 

SQL Server Execution Times: 
    CPU time = 0 ms, elapsed time = 2 ms. 
+1

+1 para la sugerencia de convertir a una función en línea. Si eso no fuera deseable por alguna razón, una alternativa es agregar una 'PRIMARY KEY' a la definición de TVF multi-declaración. También se puede reescribir la consulta como 'SELECT num FROM [20090528_tvf] .t_integer AS ti INTERSECT SELECT num FROM FROM [20090528_tvf] .fn_num (9990) AS fn FOR XML PATH ('fo'), ROOT ('q');' –

0

yo estaba de acuerdo, mesa Temp es un buen concepto. Cuando el recuento de filas aumenta en una tabla un ejemplo de 40 millones de filas y quiero actualizar varias columnas en una tabla aplicando combinaciones con otra tabla, en ese caso siempre preferiría usar la expresión de tabla común para actualizar las columnas en la instrucción seleccionada usando case statement, ahora mi select conjunto de resultados contiene filas actualizadas. Incrustar 40 millones de registros en una tabla temporal con una instrucción select usando mayúsculas y minúsculas tomó 21 minutos para mí y luego crear un índice tomó 10 minutos para que mi inserción y el tiempo de creación del índice tomaran 30 minutos . Luego voy a aplicar la actualización uniendo el conjunto de resultados actualizado de la tabla temporal con la tabla principal. Me tomó 5 minutos actualizar 10 millones de registros de 40 millones de registros, así que mi tiempo de actualización general para 10 millones de registros tomó casi 35 minutos frente a 5 minutos de la expresión de tabla común. Mi elección en ese caso es la expresión de tabla común.

Cuestiones relacionadas