Wie kommt es, dass eine Tabellenvariable unter diesen Umständen die Leistung einer Abfrage verbessert?

8

Für diesen speziellen Fall, den ich im Folgenden erläutern möchte, ist die Verwendung einer Tabellenvariablen besser als die Nichtverwendung einer Tabellenvariablen.

Ich würde gerne wissen, warum und wenn möglich, die Tabellenvariable loswerden.

Dies ist die Abfrage unter Verwendung der Tabellenvariablen:

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

Dies erzeugt die folgenden Statistiken:

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.

Mit showplan_text möchte ich den Abfrageplan anzeigen :

Erster Teil der Abfrage - Auffüllen der Tabellenvariablen Geben Sie hier die Bildbeschreibung ein

Zweiter Teil der Abfrage: Verwenden der Tabellenvariablen und Verbinden der anderen Tabellen: Geben Sie hier die Bildbeschreibung ein

Dies ist der XML-Plan der Abfrage unter Verwendung der Tabellenvariablen.

Dies ist dieselbe Abfrage, die KEINE Tabellenvariable verwendet:

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

Wenn wir uns die Statistiken ansehen, haben wir Folgendes:

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.

Nun zum Ausführungsplan im Textformat:

Einstellen der Parameter

Geben Sie hier die Bildbeschreibung ein

Nun der wichtige Teil, der die Show leitet: Geben Sie hier die Bildbeschreibung ein

Dies ist der XML-Plan der Abfrage, bei der die Tabellenvariable NICHT verwendet wird.

Aber wie kommt es, dass ich mit der Tabellenvariablen weniger Lesevorgänge und weniger E / A erhalten habe und die Ausführung (ohne den Cache zu leeren) immer schneller war?

Ich kann jedes Skript zum Erstellen einer Tabelle oder alles andere bereitstellen, was zum besseren Verständnis dieser Situation erforderlich ist.

poste einfach einen Kommentar hier und ich werde antworten.

Dies ist eine ähnliche Frage:

Warum ist die Verwendung einer Tabellenvariablen in diesem speziellen Fall mehr als doppelt so schnell wie eine # temp-Tabelle?

beim Ausführen der Abfragen nach CHECKPOINT ; DBCC DROPCLEANBUFFERS ; Die Ergebnisse waren:

Abfrage mit Tabellenvariable

Abfrage mit Tabellenvariable

Abfrage ohne Tabellenvariable

Abfrage ohne Tabellenvariable

Marcello Miorelli
quelle
Gibt es einen Unterschied, wenn Sie den Filter für die Where-Bedingung aus der ersten Abfrage in der Where-Bedingung der zweiten Abfrage beibehalten, anstatt sie in die innere Join-Bedingung zu verschieben? Dies: WO oir.decReturnFinalAmount> 0 UND o.sdtmOrdCreated> = @OrderStartDate
BateTech
@BateTech, als ich die Bedingungen aus INNER JOINS in die WHERE-Klausel verschoben habe, nachdem die Caches geleert wurden, wurde die CPU-Zeit von 203 auf 281 und die verstrichene Zeit von 865 auf 4029 erhöht. Die logischen Lesevorgänge für einige der Tabellen wurden ebenfalls erhöht .
Marcello Miorelli

Antworten:

8

Die wichtigsten Faktoren sind hier:

  • Der Optimierer versucht nicht, den besten Plan zu finden. Ziel ist es, schnell einen vernünftigen Plan zu finden
  • Es wird davon ausgegangen, dass die Abfrage mit einem kalten Cache ausgeführt wird
  • Das verwendete Kostenmodell bevorzugt sequentielle E / A gegenüber zufälligen E / A.
  • Wiederholte Suchvorgänge in einem Index werden als zufällig verteilt angenommen

Die Kardinalitätsschätzung für eine Tabellenvariable beträgt 1 Zeile (es sei denn, eine Neukompilierung auf Anweisungsebene erfolgt oder das Ablaufverfolgungsflag 2453 ist aktiv). Diese niedrige Schätzung führt zu einem sehr kostengünstigen Plan mit einer Navigationsstrategie, die auf verschachtelten Schleifen basiert. Dieser Plan kann für relativ niedrige Zeilenzahlen weiterhin wirksam sein, insbesondere wenn die benötigten Daten nicht aus dem dauerhaften Speicher eingelesen werden müssen.

Mit genaueren Kardinalitätsschätzungen bevorzugt der Optimierer einen Plan mit Hash-Joins und einigen Scans. Dies scheint angesichts der oben aufgeführten Annahmen billiger zu sein als eine Navigationsstrategie. insbesondere in Bezug auf den kalten Cache und die relativ geringen Kosten eines sequentiellen Scans im Vergleich zu vielen Suchvorgängen (unter der Annahme eines weitgehend zufälligen E / A-Musters).

Der Tabellenvariablenplan ist möglicherweise langsamer als die Alternative, wenn sich die benötigten Daten nicht im Speicher befinden - oder auch nicht . Das Kostenmodell ist genau das - ein Modell - die genauen verwendeten Zahlen sind möglicherweise nicht repräsentativ für Ihre Hardware und Konfiguration, und die getroffenen Annahmen sind unter bestimmten Umständen möglicherweise nicht gültig.

All diese Einschränkungen gelten insbesondere für kostengünstige Abfragen (beides), da kleine Kostenänderungen zu sehr unterschiedlichen Planformen führen können. Tatsächlich sind beide Pläne insofern erfolgreich, als sie schnell und effizient genug Ergebnisse liefern .

Paul White 9
quelle