Ausführungsplan zeigt teure CONVERT_IMPLICIT-Operation. Kann ich das durch Indizieren beheben oder muss ich die Tabelle ändern?

24

Ich habe eine sehr wichtige, sehr langsame Sichtweise, die einige wirklich hässliche Bedingungen wie diese in ihre where-Klausel aufnimmt. Mir ist auch bewusst, dass es sich bei den Verknüpfungen um Brutto- und langsame Verknüpfungen varchar(13)anstelle von ganzzahligen Identitätsfeldern handelt, ich möchte jedoch die folgende einfache Abfrage verbessern, die diese Ansicht verwendet:

CREATE VIEW [dbo].[vwReallySlowView]  AS  
AS  
SELECT     
  I.booking_no_v32 AS bkno, 
  I.trans_type_v41 AS trantype, 
  B.Assigned_to_v61 AS Assignbk, 
  B.order_date AS dateo, B.HourBooked AS HBooked,   
  B.MinBooked AS MBooked, B.SecBooked AS SBooked, 
  I.prep_on AS Pon, I.From_locn AS Flocn, 
  I.Trans_to_locn AS TTlocn,   
                      (CASE I.prep_on WHEN 'Y' THEN I.PDate ELSE I.FirstDate END) AS PrDate, I.PTimeH AS PrTimeH, I.PTimeM AS PrTimeM,   
                      (CASE WHEN I.RetnDate < I.FirstDate THEN I.FirstDate ELSE I.RetnDate END) AS RDatev, I.bit_field_v41 AS bitField, I.FirstDate AS FDatev, I.BookDate AS DBooked,   
                      I.TimeBookedH AS TBookH, I.TimeBookedM AS TBookM, I.TimeBookedS AS TBookS, I.del_time_hour AS dth, I.del_time_min AS dtm, I.return_to_locn AS rtlocn,   
                      I.return_time_hour AS rth, I.return_time_min AS rtm, (CASE WHEN I.Trans_type_v41 IN (6, 7) AND (I.Trans_qty < I.QtyCheckedOut)   
                      THEN 0 WHEN I.Trans_type_v41 IN (6, 7) AND (I.Trans_qty >= I.QtyCheckedOut) THEN I.Trans_Qty - I.QtyCheckedOut ELSE I.trans_qty END) AS trqty,   
                      (CASE WHEN I.Trans_type_v41 IN (6, 7) THEN 0 ELSE I.QtyCheckedOut END) AS MyQtycheckedout, (CASE WHEN I.Trans_type_v41 IN (6, 7)   
                      THEN 0 ELSE I.QtyReturned END) AS retqty, I.ID, B.BookingProgressStatus AS bkProg, I.product_code_v42, I.return_to_locn, I.AssignTo, I.AssignType,   
                      I.QtyReserved, B.DeprepOn,  
        (CASE  B.DeprepOn       
        WHEN 1 THEN  B.DeprepDateTime     
        ELSE   I.RetnDate  
           END)  AS DeprepDateTime, I.InRack 
FROM         dbo.tblItemtran AS I 

INNER JOIN  -- booking_no = varchar(13)
         dbo.tblbookings AS B ON B.booking_no = I.booking_no_v32  --  string inner-join

INNER JOIN  -- product_code = varchar(13) 
        dbo.tblInvmas AS M ON I.product_code_v42 = M.product_code  --  string inner-join

WHERE     (I.trans_type_v41 NOT IN (2, 3, 7, 18, 19, 20, 21, 12, 13, 22)) AND (I.trans_type_v41 NOT IN (6, 7)) AND (I.bit_field_v41 & 4 = 0) OR  
                      (I.trans_type_v41 NOT IN (6, 7)) AND (I.bit_field_v41 & 4 = 0) AND (B.BookingProgressStatus = 1) OR  
                      (I.trans_type_v41 IN (6, 7)) AND (I.bit_field_v41 & 4 = 0) AND (I.QtyCheckedOut = 0) OR  
                      (I.trans_type_v41 IN (6, 7)) AND (I.bit_field_v41 & 4 = 0) AND (I.QtyCheckedOut > 0) AND (I.trans_qty - (I.QtyCheckedOut - I.QtyReturned) > 0)  

Diese Ansicht wird normalerweise folgendermaßen verwendet:

select * from vwReallySlowView
where product_code_v42  = 'LIGHTBULB100W'  -- find "100 watt lightbulb" rows

Wenn ich es laufen bekomme ich diesen Artikel Ausführungsplan kostet 20 bis 80% der Gesamtkosten des Charge, mit Prädikat CONVERT_IMPLICIT( .... &(4))zeigt , dass es bei tut dies sehr langsam zu sein scheint , bitwise boolean testswie (I.ibitfield & 4 = 0).

Ich bin kein Experte in MS SQL oder bei der Arbeit mit DBAs im Allgemeinen, da ich die meiste Zeit ein Nicht-SQL-Softwareentwickler bin. Aber ich vermute, dass solche bitweisen Kombinationen eine schlechte Idee sind und dass es besser gewesen wäre, diskrete Boolesche Felder zu haben.

Könnte ich diesen Index, den ich habe, irgendwie verbessern, um diese Ansicht besser zu handhaben, ohne das Schema zu ändern (das bereits an Tausenden von Standorten in Produktion ist), oder muss ich die zugrunde liegende Tabelle ändern, die mehrere boolesche Werte enthält, die in eine Ganzzahl gepackt bit_field_v41sind, um dieses Problem zu beheben ?

Hier ist mein Clustered-Index, auf tblItemtrandem in diesem Ausführungsplan gescannt wird:

-- goal:  speed up  select * from vwReallySlowView where productcode  = 'X'
CREATE CLUSTERED INDEX [idxtblItemTranProductCodeAndTransType] ON [dbo].[tblItemtran] 
(
    [product_code_v42] ASC,  -- varchar(13)
    [trans_type_v41] ASC     -- int
)WITH ( PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF, 
        IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, 
        ALLOW_PAGE_LOCKS  = ON) 
ON [PRIMARY]

Hier ist der Ausführungsplan für eines der anderen Produkte, der zu 27% Kosten für dieses CONVERT_IMPLICITPrädikat führt. update Beachten Sie, dass in diesem Fall mein schlechtester Knoten jetzt "Hash-Übereinstimmung" auf einem inner joinist, was 34% kostet. Ich glaube, dies sind Kosten, die ich nicht vermeiden kann, es sei denn, ich könnte vermeiden, Verknüpfungen für Zeichenfolgen auszuführen, die ich derzeit nicht kann beseitigen, abschütteln. Beide INNER JOINOperationen in der obigen Ansicht beziehen sich auf varchar(13)Felder.

Unten rechts vergrößert:

Bildbeschreibung hier eingeben

Der gesamte Ausführungsplan als .sqlplan ist auf skydrive verfügbar. Dieses Bild ist nur eine visuelle Übersicht. Klicken Sie hier , um das Bild selbst zu sehen.

Bildbeschreibung hier eingeben

Aktualisieren Sie den gesamten Ausführungsplan. Ich kann anscheinend nicht feststellen, welcher product_codeWert pathologisch schlecht war, aber eine Möglichkeit, dies zu tun, besteht darin select count(*) from view, kein einzelnes Produkt zu entwickeln. Produkte, die nur in 5% der Datensätze in der zugrunde liegenden Tabelle oder weniger verwendet werden, scheinen jedoch wesentlich geringere Betriebskosten CONVERT_IMPLICIT aufzuweisen. Wenn ich die SQL hier WHEREkorrigieren würde, würde ich die Bruttoklausel in der Ansicht nehmen und das Ergebnis dieser riesigen Where-Klausel-Bedingung als "IncludeMeInTheView" -Bitfeld in der zugrunde liegenden Tabelle berechnen und speichern . Presto, Problem gelöst, oder?

Warren P
quelle
Der von mir veröffentlichte .SQLPLAN ist viel weniger pathologisch als der 432k / 17k-Fall im Originalbild. Ich entschuldige mich dafür, dass ich den product_codeWert, den ich zur Generierung dieses pathologischen 87%Falls verwendet habe, mit meinen Daten nicht mehr finden kann. Die Bilder zeigen jetzt 27%. Nochmals eine Entschuldigung für die Verwirrung aufgrund meiner Änderungen.
Warren P

Antworten:

49

Sie sollten sich in Ausführungsplänen nicht zu sehr auf die prozentualen Kosten verlassen. Dies sind immer geschätzte Kosten , auch in Post-Execution-Plänen mit 'tatsächlichen' Zahlen für Dinge wie Zeilenanzahl. Die geschätzten Kosten basieren auf einem Modell, das für den beabsichtigten Zweck ziemlich gut funktioniert: dem Optimierer zu ermöglichen, zwischen verschiedenen Kandidatenausführungsplänen für dieselbe Abfrage zu wählen. Die Kosteninformationen sind interessant und ein zu berücksichtigender Faktor, sollten jedoch selten eine primäre Metrik für die Abfrageoptimierung sein. Das Interpretieren von Ausführungsplaninformationen erfordert eine breitere Sicht auf die präsentierten Daten.

ItemTran Clustered Index Suchoperator

ItemTran Clustered Index Seek

Dieser Operator ist wirklich zwei Operationen in einer. Zuerst findet eine Indexsuchoperation alle Zeilen, die mit dem Prädikat übereinstimmen product_code_v42 = 'M10BOLT', dann wird auf jede Zeile das verbleibende Prädikat bit_field_v41 & 4 = 0angewendet. Es gibt eine implizite Konvertierung von bit_field_v41von seinem Basistyp ( tinyintoder smallint) nach integer.

Die Konvertierung erfolgt, weil der bitweise AND-Operator (&) erfordert, dass beide Operanden vom gleichen Typ sind. Der implizite Typ des Konstantenwerts '4' ist eine Ganzzahl, und die Vorrangregeln für Datentypen bedeuten, dass der Feldwert mit der niedrigeren Priorität bit_field_v41konvertiert wird.

Das Problem (wie es ist) kann leicht behoben werden, indem das Prädikat wie folgt geschrieben wird: bit_field_v41 & CONVERT(tinyint, 4) = 0- Der konstante Wert hat die niedrigere Priorität und wird (während der konstanten Faltung) anstelle des Spaltenwerts konvertiert. Wenn das bit_field_v41ist tinyintauftreten keine Wandlungen überhaupt. Ebenso CONVERT(smallint, 4)könnte verwendet werden, wenn bit_field_v41ist smallint. Allerdings ist die Konvertierung in diesem Fall kein Leistungsproblem , es ist jedoch nach wie vor empfehlenswert, Typen zuzuordnen und implizite Konvertierungen nach Möglichkeit zu vermeiden.

Der größte Teil der geschätzten Kosten für diese Suche hängt von der Größe der Basistabelle ab. Während der gruppierte Indexschlüssel selbst ziemlich schmal ist, ist die Größe jeder Zeile groß. Eine Definition für die Tabelle wird nicht angegeben, sondern nur die in der Ansicht verwendeten Spalten summieren sich zu einer signifikanten Zeilenbreite. Da der gruppierte Index alle Spalten enthält, entspricht der Abstand zwischen gruppierten Indexschlüsseln der Breite der Zeile und nicht der Breite der Indexschlüssel . Die Verwendung von Versionssuffixen für einige Spalten legt nahe, dass die reale Tabelle für frühere Versionen noch mehr Spalten enthält.

Betrachtet man die Such-, Residuenprädikat- und Ausgabespalten, so kann die Leistung dieses Operators isoliert überprüft werden, indem eine entsprechende Abfrage erstellt wird (dies 1 <> 2ist ein Trick, um die automatische Parametrisierung zu verhindern. Der Widerspruch wird vom Optimierer entfernt und erscheint nicht in der Abfrageplan):

SELECT
    it.booking_no_v32,
    it.QtyCheckedOut,
    it.QtyReturned,
    it.Trans_qty,
    it.trans_type_v41
FROM dbo.tblItemTran AS it
WHERE
    1 <> 2
    AND it.product_code_v42 = 'M10BOLT'
    AND it.bit_field_v41 & CONVERT(tinyint, 4) = 0;

Die Leistung dieser Abfrage mit einem kalten Datencache ist von Interesse, da das Vorauslesen durch die Fragmentierung von Tabellen (Clustered-Index) beeinträchtigt wird. Der Clustering-Schlüssel für diese Tabelle fordert zur Fragmentierung auf. Daher kann es wichtig sein, diesen Index regelmäßig zu verwalten (neu zu organisieren oder zu erstellen) und einen geeigneten FILLFACTORPlatz für neue Zeilen zwischen den Indexverwaltungsfenstern zu verwenden.

Ich habe den Effekt der Fragmentierung auf das Vorauslesen anhand von Beispieldaten getestet, die mit SQL Data Generator generiert wurden . Bei Verwendung der gleichen Anzahl von Tabellenzeilen wie im Abfrageplan der Frage dauerte ein stark fragmentierter Clustered-Index SELECT * FROM view15 Sekunden DBCC DROPCLEANBUFFERS. Dieselbe Prüfung unter denselben Bedingungen mit einem neu erstellten Clustered-Index für die ItemTrans-Tabelle wurde in 3 Sekunden abgeschlossen.

Wenn sich die Tabellendaten normalerweise vollständig im Cache befinden, ist das Fragmentierungsproblem sehr viel weniger wichtig. Aber selbst bei geringer Fragmentierung können die breiten Tabellenzeilen bedeuten, dass die Anzahl der logischen und physischen Lesevorgänge viel höher ist als erwartet. Sie können auch mit dem Hinzufügen und Entfernen des Explicit experimentieren CONVERT, um meine Erwartung zu bestätigen, dass das implizite Konvertierungsproblem hier nicht wichtig ist, außer als Best-Practice-Verstoß.

Wesentlicher ist die geschätzte Anzahl der Zeilen, die den Suchoperator verlassen. Die Optimierungszeitschätzung beträgt 165 Zeilen, aber zur Ausführungszeit wurden 4.226 Zeilen erstellt. Ich werde später auf diesen Punkt zurückkommen, aber der Hauptgrund für die Diskrepanz ist, dass die Selektivität des verbleibenden Prädikats (einschließlich des bitweisen UND) für den Optimierer sehr schwer vorherzusagen ist - in der Tat greift er auf Vermutungen zurück.

Filter Operator

Filter Operator

Ich zeige das Filter-Prädikat hier hauptsächlich, um zu veranschaulichen, wie die beiden NOT INListen kombiniert, vereinfacht und dann erweitert werden, und um eine Referenz für die folgende Hash-Match-Diskussion bereitzustellen. Die Testabfrage aus der Suche kann erweitert werden, um ihre Auswirkungen einzubeziehen und die Auswirkung des Filteroperators auf die Leistung zu bestimmen:

SELECT
    it.booking_no_v32,
    it.trans_type_v41,
    it.Trans_qty,
    it.QtyReturned,
    it.QtyCheckedOut
FROM dbo.tblItemTran AS it
WHERE
    it.product_code_v42 = 'M10BOLT'
    AND it.bit_field_v41 & CONVERT(tinyint, 4) = 0
    AND
    (
        (
            it.trans_type_v41 NOT IN (2, 3, 6, 7, 18, 19, 20, 21, 12, 13, 22)
            AND it.trans_type_v41 NOT IN (6, 7)
        )
        OR
        (
            it.trans_type_v41 NOT IN (6, 7)
        )
        OR 
        (
            it.trans_type_v41 IN (6, 7)
            AND it.QtyCheckedOut = 0
        )
        OR 
        (
            it.trans_type_v41 IN (6, 7)
            AND it.QtyCheckedOut > 0
            AND it.trans_qty - (it.QtyCheckedOut - it.QtyReturned) > 0
        )
    );

Der Compute Scalar-Operator im Plan definiert den folgenden Ausdruck (die Berechnung selbst wird zurückgestellt, bis das Ergebnis von einem späteren Operator angefordert wird):

[Expr1016] = (trans_qty - (QtyCheckedOut - QtyReturned))

Der Hash-Match-Operator

Das Durchführen eines Joins für Zeichendatentypen ist nicht der Grund für die hohen geschätzten Kosten dieses Operators. Die SSMS-QuickInfo zeigt nur einen Hash Keys Probe-Eintrag an, die wichtigen Details befinden sich jedoch im Fenster SSMS-Eigenschaften.

Der Hash-Match-Operator erstellt eine Hash-Tabelle mit den Werten der booking_no_v32Spalte (Hash Keys Build) aus der ItemTran-Tabelle und sucht dann nach Übereinstimmungen mit der booking_noSpalte (Hash Keys Probe) aus der Bookings-Tabelle. Der SSMS-Tooltip zeigt normalerweise auch einen Test-Residuum an, aber der Text ist viel zu lang für einen Tooltip und wird einfach weggelassen.

Ein Test-Residuum ähnelt dem Residuum, das nach der früheren Indexsuche angezeigt wird. Das verbleibende Vergleichselement wird für alle Zeilen ausgewertet, die mit dem Hash übereinstimmen, um zu bestimmen, ob die Zeile an den übergeordneten Operator übergeben werden soll. Das Finden von Hash-Übereinstimmungen in einer ausgewogenen Hash-Tabelle ist extrem schnell, aber das Anwenden eines komplexen Residuenprädikats auf jede übereinstimmende Zeile ist im Vergleich dazu recht langsam. In der QuickInfo "Hash Match" im Plan-Explorer werden die Details einschließlich des Ausdrucks "Probe Residual" angezeigt:

Hash-Match-Operator

Das verbleibende Vergleichselement ist komplex und enthält die Überprüfung des Buchungsfortschritts, sobald die Spalte in der Buchungstabelle verfügbar ist. Der Tooltip zeigt auch die gleiche Diskrepanz zwischen der geschätzten und der tatsächlichen Zeilenanzahl, die zuvor bei der Indexsuche festgestellt wurde. Es mag seltsam erscheinen, dass ein Großteil der Filterung zweimal durchgeführt wird, aber dies ist nur der Optimierer, der optimistisch ist. Es wird nicht erwartet, dass die Teile des Filters, die vom Sondenrest im Plan nach unten verschoben werden können, alle Zeilen entfernen (die Schätzungen der Zeilenanzahl sind vor und nach dem Filter gleich), aber der Optimierer weiß, dass dies möglicherweise falsch ist. Die Chance, Zeilen frühzeitig zu filtern (wodurch die Kosten für den Hash-Join gesenkt werden), ist die geringen Kosten des zusätzlichen Filters wert. Der gesamte Filter kann nicht heruntergedrückt werden, da er einen Test für eine Spalte aus der Buchungstabelle enthält, der größte Teil jedoch.

Die Unterschätzung der Zeilenanzahl ist ein Problem für den Hash-Match-Operator, da die für die Hash-Tabelle reservierte Speicherkapazität von der geschätzten Anzahl der Zeilen abhängt. Wenn der Speicher für die zur Laufzeit erforderliche Größe der Hash-Tabelle zu klein ist (aufgrund der größeren Anzahl von Zeilen), wird die Hash-Tabelle rekursiv in den physischen Tempdb- Speicher verschoben , was häufig zu einer sehr schlechten Leistung führt. Im schlimmsten Fall stoppt die Ausführungs-Engine das rekursive Verschütten von Hash-Buckets und greift auf einen sehr langsamen zurückRettungsalgorithmus. Hash-Spilling (rekursiv oder Bailout) ist die wahrscheinlichste Ursache für die in der Frage beschriebenen Leistungsprobleme (keine zeichenartigen Join-Spalten oder impliziten Konvertierungen). Die Hauptursache ist, dass der Server aufgrund einer falschen Schätzung der Zeilenanzahl (Kardinalität) zu wenig Speicher für die Abfrage reserviert.

Leider gibt es vor SQL Server 2012 im Ausführungsplan keinen Hinweis darauf, dass ein Hashvorgang seine Speicherzuordnung überschritten hat (die nach der Reservierung vor dem Start der Ausführung nicht dynamisch anwachsen kann, selbst wenn der Server über eine große Menge an freiem Speicher verfügt) und überflüssig werden musste tempdb. Es ist möglich, die Hash Warning Event Class mit dem Profiler zu überwachen , es kann jedoch schwierig sein, die Warnungen mit einer bestimmten Abfrage zu korrelieren.

Behebung der Probleme

Die drei Probleme sind die Fragmentierung, der komplexe Sondenrest im Hash-Match-Operator und die falsche Kardinalitätsschätzung, die sich aus dem Erraten der Indexsuche ergibt.

Empfohlene Lösung

Überprüfen Sie die Fragmentierung und korrigieren Sie sie gegebenenfalls. Planen Sie die Wartung, um sicherzustellen, dass der Index akzeptabel organisiert bleibt. Die übliche Methode zur Korrektur der Kardinalitätsschätzung besteht in der Bereitstellung von Statistiken. In diesem Fall benötigt der Optimierer Statistiken für die Kombination ( product_code_v42, bitfield_v41 & 4 = 0). Wir können keine Statistiken für einen Ausdruck direkt erstellen, daher müssen wir zuerst eine berechnete Spalte für den Bitfeldausdruck erstellen und dann die manuelle mehrspaltige Statistik erstellen:

ALTER TABLE dbo.tblItemTran
ADD Bit3 AS bit_field_v41 & CONVERT(tinyint, 4);

CREATE STATISTICS [stats dbo.ItemTran (product_code_v42, Bit3)]
ON dbo.tblItemTran (product_code_v42, Bit3);

Die berechnete Spaltentextdefinition muss mit dem Text in der Ansichtsdefinition ziemlich genau übereinstimmen, damit die Statistiken verwendet werden. Korrigieren Sie daher gleichzeitig die Ansicht, um die implizite Konvertierung zu beseitigen, und achten Sie darauf, dass der Text übereinstimmt.

Die mehrspaltigen Statistiken sollten zu viel besseren Schätzungen führen und die Wahrscheinlichkeit, dass der Hash-Match-Operator rekursives Verschütten oder den Rettungsalgorithmus verwendet, erheblich verringern. Das Hinzufügen der berechneten Spalte (bei der es sich nur um eine Metadatenoperation handelt und die keinen Platz in der Tabelle beansprucht, da sie nicht markiert ist PERSISTED) und der mehrspaltigen Statistik ist meine beste Vermutung für eine erste Lösung.

Bei der Lösung von Problemen mit der Abfrageleistung ist es wichtig, die verstrichene Zeit, die CPU-Auslastung, die logischen Lesevorgänge, die physischen Lesevorgänge, die Wartezeiten und -dauern usw. zu messen. Es kann auch nützlich sein, Teile der Abfrage separat auszuführen, um vermutete Ursachen wie oben dargestellt zu überprüfen.

In einigen Umgebungen, in denen eine sekundengenaue Ansicht der Daten nicht wichtig ist, kann es nützlich sein, gelegentlich einen Hintergrundprozess auszuführen, der die gesamte Ansicht in eine Momentaufnahmetabelle umwandelt. Diese Tabelle ist nur eine normale Basistabelle und kann für Leseabfragen indiziert werden, ohne dass die Aktualisierungsleistung beeinträchtigt werden muss.

Indexierung anzeigen

Versuchen Sie nicht, die ursprüngliche Ansicht direkt zu indizieren. Die Leseleistung ist erstaunlich schnell (eine einzelne Suche in einem Ansichtsindex), aber (in diesem Fall) werden alle Leistungsprobleme in den vorhandenen Abfrageplänen auf Abfragen übertragen, die eine der Tabellenspalten ändern, auf die in der Ansicht verwiesen wird. Abfragen, die die Zeilen der Basistabelle ändern, sind in der Tat sehr stark betroffen.

Erweiterte Lösung mit einer teilweise indizierten Ansicht

Es gibt eine teilweise indizierte Ansichtslösung für diese spezielle Abfrage, die Kardinalitätsschätzungen korrigiert und die Filter- und Sondenreste entfernt. Sie basiert jedoch auf einigen Annahmen zu den Daten (meistens meine Vermutung des Schemas) und erfordert eine fachmännische Implementierung, insbesondere in Bezug auf die Eignung Indizes zur Unterstützung der Wartungspläne für die indizierte Ansicht. Ich teile den folgenden Code aus Interesse, ich schlage nicht vor, ihn ohne sehr sorgfältige Analyse und Prüfung zu implementieren .

-- Indexed view to optimize the main view
CREATE VIEW dbo.V1
WITH SCHEMABINDING
AS
SELECT
    it.ID,
    it.product_code_v42,
    it.trans_type_v41,
    it.booking_no_v32,
    it.Trans_qty,
    it.QtyReturned,
    it.QtyCheckedOut,
    it.QtyReserved,
    it.bit_field_v41,
    it.prep_on,
    it.From_locn,
    it.Trans_to_locn,
    it.PDate,
    it.FirstDate,
    it.PTimeH,
    it.PTimeM,
    it.RetnDate,
    it.BookDate,
    it.TimeBookedH,
    it.TimeBookedM,
    it.TimeBookedS,
    it.del_time_hour,
    it.del_time_min,
    it.return_to_locn,
    it.return_time_hour,
    it.return_time_min,
    it.AssignTo,
    it.AssignType,
    it.InRack
FROM dbo.tblItemTran AS it
JOIN dbo.tblBookings AS tb ON
    tb.booking_no = it.booking_no_v32
WHERE
    (
        it.trans_type_v41 NOT IN (2, 3, 7, 18, 19, 20, 21, 12, 13, 22)
        AND it.trans_type_v41 NOT IN (6, 7)
        AND it.bit_field_v41 & CONVERT(tinyint, 4) = 0
    )
    OR
    (
        it.trans_type_v41 NOT IN (6, 7)
        AND it.bit_field_v41 & CONVERT(tinyint, 4) = 0
        AND tb.BookingProgressStatus = 1
    )
    OR 
    (
        it.trans_type_v41 IN (6, 7)
        AND it.bit_field_v41 & CONVERT(tinyint, 4) = 0
        AND it.QtyCheckedOut = 0
    )
    OR 
    (
        it.trans_type_v41 IN (6, 7)
        AND it.bit_field_v41 & CONVERT(tinyint, 4) = 0
        AND it.QtyCheckedOut > 0
        AND it.trans_qty - (it.QtyCheckedOut - it.QtyReturned) > 0
    );
GO
CREATE UNIQUE CLUSTERED INDEX cuq ON dbo.V1 (product_code_v42, ID);
GO

Die vorhandene Ansicht wurde optimiert, um die oben angegebene indizierte Ansicht zu verwenden:

CREATE VIEW [dbo].[vwReallySlowView2]
AS
SELECT
    I.booking_no_v32 AS bkno,
    I.trans_type_v41 AS trantype,
    B.Assigned_to_v61 AS Assignbk,
    B.order_date AS dateo,
    B.HourBooked AS HBooked,
    B.MinBooked AS MBooked,
    B.SecBooked AS SBooked,
    I.prep_on AS Pon,
    I.From_locn AS Flocn,
    I.Trans_to_locn AS TTlocn,
    CASE I.prep_on 
        WHEN 'Y' THEN I.PDate
        ELSE I.FirstDate
    END AS PrDate,
    I.PTimeH AS PrTimeH,
    I.PTimeM AS PrTimeM,
    CASE
        WHEN I.RetnDate < I.FirstDate 
        THEN I.FirstDate 
        ELSE I.RetnDate
    END AS RDatev,
    I.bit_field_v41 AS bitField,
    I.FirstDate AS FDatev,
    I.BookDate AS DBooked,
    I.TimeBookedH AS TBookH,
    I.TimeBookedM AS TBookM,
    I.TimeBookedS AS TBookS,
    I.del_time_hour AS dth,
    I.del_time_min AS dtm,
    I.return_to_locn AS rtlocn,
    I.return_time_hour AS rth,
    I.return_time_min AS rtm,
    CASE
        WHEN
            I.Trans_type_v41 IN (6, 7) 
            AND I.Trans_qty < I.QtyCheckedOut
            THEN 0 
        WHEN
            I.Trans_type_v41 IN (6, 7)
            AND I.Trans_qty >= I.QtyCheckedOut
            THEN I.Trans_Qty - I.QtyCheckedOut
        ELSE
            I.trans_qty
    END AS trqty,
    CASE
        WHEN I.Trans_type_v41 IN (6, 7)
        THEN 0
        ELSE I.QtyCheckedOut
    END AS MyQtycheckedout,
    CASE
        WHEN I.Trans_type_v41 IN (6, 7)
        THEN 0
        ELSE I.QtyReturned
    END AS retqty,
    I.ID,
    B.BookingProgressStatus AS bkProg,
    I.product_code_v42,
    I.return_to_locn,
    I.AssignTo,
    I.AssignType,
    I.QtyReserved,
    B.DeprepOn,
    CASE B.DeprepOn
        WHEN 1 THEN B.DeprepDateTime
        ELSE I.RetnDate
    END AS DeprepDateTime,
    I.InRack
FROM dbo.V1 AS I WITH (NOEXPAND)
JOIN dbo.tblbookings AS B ON
    B.booking_no = I.booking_no_v32
JOIN dbo.tblInvmas AS M ON
    I.product_code_v42 = M.product_code;

Beispielabfrage und Ausführungsplan:

SELECT
    vrsv.*
FROM dbo.vwReallySlowView2 AS vrsv
WHERE vrsv.product_code_v42 = 'M10BOLT';

Neuer Ausführungsplan

Im neuen Plan hat die Hash-Übereinstimmung kein Residuum-Prädikat , es gibt keinen komplexen Filter , kein Residuum-Prädikat für die Suche in der indizierten Ansicht und die Kardinalitätsschätzungen sind genau richtig.

Beispiel für die Auswirkung des Einfügens / Aktualisierens / Löschens von Plänen: Dies ist der Plan für ein Einfügen in die ItemTrans-Tabelle:

Plan einfügen

Der hervorgehobene Abschnitt ist neu und für die Pflege der indizierten Ansicht erforderlich. Der Tabellenspool gibt eingefügte Basistabellenzeilen für die Pflege indizierter Sichten wieder. Jede Zeile wird mithilfe einer Clustered-Index-Suche mit der Buchungstabelle verbunden. Anschließend wendet ein Filter die WHEREPrädikate für komplexe Klauseln an, um festzustellen, ob die Zeile der Ansicht hinzugefügt werden muss. In diesem Fall wird eine Einfügung in den Clustered-Index der Ansicht vorgenommen.

Derselbe SELECT * FROM viewzuvor durchgeführte Test wurde in 150 ms durchgeführt, wobei die indizierte Ansicht vorhanden war.

Abschließend stelle ich fest, dass sich Ihr 2008 R2-Server noch bei RTM befindet. Es wird Ihre Leistungsprobleme nicht beheben, aber Service Pack 2 für 2008 R2 ist seit Juli 2012 verfügbar, und es gibt viele gute Gründe, mit Service Packs so aktuell wie möglich zu bleiben.

Paul White sagt GoFundMonica
quelle