¿Cómo es que una variable de tabla está mejorando el rendimiento de una consulta en esta circunstancia?

8

para este caso específico, que trataré de explicar a continuación, usar una variable de tabla funciona mejor que no usar una variable de tabla.

Me gustaría saber por qué, y si es posible, deshacerme de la variable de tabla.

Esta es la consulta usando la variable de tabla:

USE [BISource_UAT]
GO

set statistics io on
SET STATISTICS TIME ON

    SET NOCOUNT ON;

    DECLARE @OrderStartDate DATETIME = '15-feb-2015'
    DECLARE @OrderEndDate DATETIME = '28-feb-2016'
    DECLARE @tmp TABLE
    (
    strBxOrderNo VARCHAR(20)
    ,sintReturnId INT
    )  

    INSERT INTO @tmp
    SELECT  strBxOrderNo
            ,sintReturnId
    FROM    TABLEBACKUPS.dbo.tblBReturnHistory rh
    WHERE   rh.sintReturnStatusId in ( 3 )
    AND     rh.dtmAdded >= @OrderStartDate
    AND     rh.dtmAdded < @OrderEndDate

    SELECT 
             op.lngPaymentID
            ,op.strBxOrderNo
            ,op.sintPaymentTypeID
            ,op.strCurrencyCode
            ,op.strBCCurrencyCode
            ,op.decPaymentAmount
            ,op.decBCPaymentAmount
            ,ap.strAccountCode
            ,o.sintMarketID
            ,o.sintOrderChannelID
            ,o.sintOrderTypeID
            ,CASE   WHEN opgv.lngpaymentID IS NULL THEN NULL
                     -- Not a Voucher = Null
                WHEN gvp.strIssuedBxOrderNo IS NULL THEN 0 ELSE 1 
              END AS [IsPromoVoucher] -- Is a Voucher - check type
            ,o.sdtmOrdCreated

    FROM    @tmp rh

            INNER JOIN TABLEBACKUPS.dbo.tblBReturn r 
                    ON r.sintReturnId = rh.sintReturnId 
                   AND r.strBxOrderNo = rh.strBxOrderNo

            INNER JOIN bocss2.dbo.tblBOrder o 
                    ON o.strBxOrderNo = r.strBxOrderNo

            INNER JOIN Bocss2.dbo.tblBOrderPayment op 
                    ON op.strBxOrderNo = o.strBxOrderNo

            INNER JOIN TABLEBACKUPS.dbo.tblBOrderItemReturn AS oir 
                    ON r.sintReturnId = oir.sintReturnID 
                   AND r.strBxOrderNo = oir.strBxOrderNo

            INNER JOIN Bocss2.dbo.tblBOrderItem AS i 
                    ON i.strBxOrderNo = oir.strBxOrderNo 
                   AND i.sintOrderSeqNo = oir.sintOrderSeqNo

            INNER JOIN TABLEBACKUPS.dbo.tblBAccountParticipant ap 
                   ON o.lngAccountParticipantID = ap.lngParticipantID

            LEFT OUTER JOIN TABLEBACKUPS.dbo.tblBOrderPaymentGiftVoucher opgv 
                         ON op.lngPaymentID = opgv.lngPaymentID

            LEFT OUTER JOIN TABLEBACKUPS.dbo.tblBGiftVoucher gv 
                         ON opgv.strVoucherNumber = gv.strVoucherNumber

            LEFT OUTER JOIN TABLEBACKUPS.dbo.tblBGiftVoucherPromotion gvp 
                         ON gvp.strIssuedBxOrderNo = gv.strIssuedBxOrderNo

    WHERE   oir.decReturnFinalAmount > 0
    AND     o.sdtmOrdCreated >= @OrderStartDate

esto produce las siguientes estadísticas:

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

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

 SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 0 ms.
SQL Server parse and compile time: 
   CPU time = 78 ms, elapsed time = 86 ms.

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

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

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

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

 SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 0 ms.
Table '#BF0B2154'. Scan count 0, logical reads 1957, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'tblBReturnHistory'. Scan count 1, logical reads 13, 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 = 16 ms,  elapsed time = 9 ms.
Table 'tblBGiftVoucherPromotion'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'tblBGiftVoucher'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'tblBOrderPaymentGiftVoucher'. Scan count 0, logical reads 452, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'tblBOrderItem'. Scan count 0, logical reads 904, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'tblBOrderPayment'. Scan count 186, logical reads 1649, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'tblBAccountParticipant'. Scan count 0, logical reads 7112, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'tblBOrder'. Scan count 3557, logical reads 14267, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'tblBOrderItemReturn'. Scan count 1951, logical reads 5865, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'tblBReturn'. Scan count 0, logical reads 3902, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table '#BF0B2154'. Scan count 1, logical reads 7, 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 = 125 ms,  elapsed time = 138 ms.
SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 0 ms.

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

usando showplan_text en Me gustaría mostrar el plan de consulta:

primera parte de la consulta: completar la variable de tabla ingrese la descripción de la imagen aquí

segunda parte de la consulta: usando la variable de tabla y uniendo las otras tablas: ingrese la descripción de la imagen aquí

Este es el plan XML de la consulta usando la variable de tabla.

ahora esta es la misma consulta que NO usa una variable de tabla:

USE [BISource_UAT]
GO

set statistics io on
SET STATISTICS TIME ON

    SET NOCOUNT ON;

    DECLARE @OrderStartDate DATETIME = '15-feb-2015'
    DECLARE @OrderEndDate DATETIME = '28-feb-2016'

    SELECT 
             op.lngPaymentID
            ,op.strBxOrderNo
            ,op.sintPaymentTypeID
            ,op.strCurrencyCode
            ,op.strBCCurrencyCode
            ,op.decPaymentAmount
            ,op.decBCPaymentAmount
            ,ap.strAccountCode
            ,o.sintMarketID
            ,o.sintOrderChannelID
            ,o.sintOrderTypeID
            ,CASE   WHEN opgv.lngpaymentID IS NULL 
               THEN NULL -- Not a Voucher = Null
                WHEN gvp.strIssuedBxOrderNo IS NULL 
                THEN 0 ELSE 1 END AS [IsPromoVoucher] 
                -- Is a Voucher - check type
            ,o.sdtmOrdCreated

    FROM    TABLEBACKUPS.dbo.tblBReturnHistory rh

            INNER JOIN TABLEBACKUPS.dbo.tblBReturn r 
                    ON r.sintReturnId = rh.sintReturnId 
                   AND r.strBxOrderNo = rh.strBxOrderNo

            INNER JOIN bocss2.dbo.tblBOrder o 
                    ON o.strBxOrderNo = r.strBxOrderNo
                   AND o.sdtmOrdCreated >= @OrderStartDate

            INNER JOIN Bocss2.dbo.tblBOrderPayment op 
                    ON op.strBxOrderNo = o.strBxOrderNo

            INNER JOIN TABLEBACKUPS.dbo.tblBOrderItemReturn AS oir 
                    ON r.sintReturnId = oir.sintReturnID 
                   AND r.strBxOrderNo = oir.strBxOrderNo
                   AND oir.decReturnFinalAmount > 0

            INNER JOIN Bocss2.dbo.tblBOrderItem AS i 
                    ON i.strBxOrderNo = oir.strBxOrderNo 
                   AND i.sintOrderSeqNo = oir.sintOrderSeqNo

            INNER JOIN TABLEBACKUPS.dbo.tblBAccountParticipant ap 
                   ON o.lngAccountParticipantID = ap.lngParticipantID

            LEFT OUTER JOIN TABLEBACKUPS.dbo.tblBOrderPaymentGiftVoucher opgv 
                         ON op.lngPaymentID = opgv.lngPaymentID

            LEFT OUTER JOIN TABLEBACKUPS.dbo.tblBGiftVoucher gv 
                         ON opgv.strVoucherNumber = gv.strVoucherNumber

            LEFT OUTER JOIN TABLEBACKUPS.dbo.tblBGiftVoucherPromotion gvp 
                         ON gvp.strIssuedBxOrderNo = gv.strIssuedBxOrderNo

    WHERE   rh.sintReturnStatusId in ( 3 )
    AND     rh.dtmAdded >= @OrderStartDate
    AND     rh.dtmAdded < @OrderEndDate

Al echar un vistazo a las estadísticas, esto es lo que tenemos:

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

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

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

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

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

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

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

 SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 0 ms.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'tblBGiftVoucher'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'tblBAccountParticipant'. Scan count 1, logical reads 32, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'tblBReturn'. Scan count 1, logical reads 170, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'tblBOrderItemReturn'. Scan count 0, logical reads 35849, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'tblBOrderPayment'. Scan count 9408, logical reads 87643, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'tblBOrderItem'. Scan count 1950, logical reads 8336, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'tblBOrder'. Scan count 1951, logical reads 7835, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'tblBReturnHistory'. Scan count 1, logical reads 13, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'tblBOrderPaymentGiftVoucher'. Scan count 1, logical reads 4, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'tblBGiftVoucherPromotion'. Scan count 1, logical reads 27, 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 = 625 ms,  elapsed time = 612 ms.
SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 0 ms.

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

Ahora, con respecto al plan de ejecución en formato de texto:

establecer los parámetros

ingrese la descripción de la imagen aquí

Ahora la parte importante, ejecutar el espectáculo: ingrese la descripción de la imagen aquí

Este es el plan XML de la consulta que NO utiliza la variable de tabla.

Pero, ¿por qué al usar la variable de tabla obtuve menos lecturas, menos E / S y la ejecución (sin borrar el caché) siempre ha sido más rápida?

Puedo proporcionar cualquier script de creación de tabla, o cualquier otra cosa necesaria para una mejor comprensión de esta situación.

simplemente publique cualquier comentario aquí y le responderé.

Esta es una pregunta similar:

¿Por qué está usando una variable de tabla más del doble de rápido que una tabla #temp en este caso específico?

al ejecutar las consultas después de CHECKPOINT ; DBCC DROPCLEANBUFFERS ; los resultados fueron:

consulta con variable de tabla

consulta con variable de tabla

consulta sin variable de tabla

consulta sin variable de tabla

Marcello Miorelli
fuente
¿Hay alguna diferencia si mantiene el filtro de condición where de la primera consulta en la condición where de la segunda consulta en lugar de moverlos a la condición de unión interna? Esto: DONDE oir.decReturnFinalAmount> 0 AND o.sdtmOrdCreated> = @OrderStartDate
BateTech
@BateTech cuando trasladé las condiciones del interior de INNER JOINS a la cláusula WHERE, después de borrar los cachés, el tiempo de CPU aumentó de 203 a 281 y el tiempo transcurrido aumentó de 865 a 4029. Las lecturas lógicas para algunas de las tablas también aumentaron .
Marcello Miorelli

Respuestas:

8

Los principales factores en juego aquí son:

  • El optimizador no intenta encontrar el mejor plan; su objetivo es encontrar un plan razonable rápidamente
  • Se supone que la consulta se ejecutará con un caché frío
  • El modelo de costo utilizado favorece la E / S secuencial sobre la E / S aleatoria
  • Se supone que las búsquedas repetidas en un índice se distribuyen aleatoriamente

La estimación de cardinalidad para una variable de tabla es de 1 fila (a menos que se produzca una recompilación de nivel de instrucción o el indicador de seguimiento 2453 esté activo). Esta baja estimación da como resultado un plan de muy bajo costo, que presenta una estrategia de navegación basada en bucles anidados. Este plan puede continuar siendo efectivo para recuentos de filas relativamente bajos, especialmente si los datos necesarios no necesitan ser leídos desde un almacenamiento persistente.

Con estimaciones de cardinalidad más precisas, el optimizador favorece un plan utilizando combinaciones hash y algunos escaneos. Esto parece ser más barato que una estrategia de navegación, dados los supuestos enumerados anteriormente; especialmente en relación con la memoria caché en frío y el costo relativamente bajo de una exploración secuencial en comparación con muchas búsquedas (suponiendo un patrón de E / S en gran medida aleatorio).

El plan variable de la tabla puede ser más lento que la alternativa si los datos necesarios no están en la memoria, o puede que no . El modelo de costo es exactamente eso, un modelo, los números exactos utilizados pueden no ser representativos de su hardware y configuración, y las suposiciones hechas pueden no ser válidas en las circunstancias particulares.

Todas estas advertencias se aplican especialmente a consultas de bajo costo (que son ambas) ya que los pequeños cambios de costo pueden producir formas de plan muy diferentes. De hecho, ambos planes tienen éxito en que producen resultados con rapidez y eficacia suficiente .

Paul White 9
fuente