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 tests
wie (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_v41
sind, um dieses Problem zu beheben ?
Hier ist mein Clustered-Index, auf tblItemtran
dem 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_IMPLICIT
Prädikat führt. update Beachten Sie, dass in diesem Fall mein schlechtester Knoten jetzt "Hash-Übereinstimmung" auf einem inner join
ist, 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 JOIN
Operationen in der obigen Ansicht beziehen sich auf varchar(13)
Felder.
Unten rechts vergrößert:
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.
Aktualisieren Sie den gesamten Ausführungsplan. Ich kann anscheinend nicht feststellen, welcher product_code
Wert 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 WHERE
korrigieren 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?
quelle
product_code
Wert, den ich zur Generierung dieses pathologischen87%
Falls verwendet habe, mit meinen Daten nicht mehr finden kann. Die Bilder zeigen jetzt27%
. Nochmals eine Entschuldigung für die Verwirrung aufgrund meiner Änderungen.Antworten:
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
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ädikatbit_field_v41 & 4 = 0
angewendet. Es gibt eine implizite Konvertierung vonbit_field_v41
von seinem Basistyp (tinyint
odersmallint
) nachinteger
.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_v41
konvertiert 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 dasbit_field_v41
isttinyint
auftreten keine Wandlungen überhaupt. EbensoCONVERT(smallint, 4)
könnte verwendet werden, wennbit_field_v41
istsmallint
. 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 <> 2
ist ein Trick, um die automatische Parametrisierung zu verhindern. Der Widerspruch wird vom Optimierer entfernt und erscheint nicht in der Abfrageplan):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
FILLFACTOR
Platz 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 view
15 SekundenDBCC 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
Ich zeige das Filter-Prädikat hier hauptsächlich, um zu veranschaulichen, wie die beiden
NOT IN
Listen 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: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):
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_v32
Spalte (Hash Keys Build) aus der ItemTran-Tabelle und sucht dann nach Übereinstimmungen mit derbooking_no
Spalte (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:
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: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 .
Die vorhandene Ansicht wurde optimiert, um die oben angegebene indizierte Ansicht zu verwenden:
Beispielabfrage und 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:
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
WHERE
Prä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 view
zuvor 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.
quelle