Ich habe eine große Ansicht, die ich innerhalb einer Anwendung verwende. Ich glaube, ich habe mein Leistungsproblem eingegrenzt, bin mir aber nicht sicher, wie ich es beheben soll. Eine vereinfachte Version der Ansicht sieht folgendermaßen aus:
SELECT ISNULL(SEId + '-' + PEId, '0-0') AS Id,
*,
DATEADD(minute, Duration, EventTime) AS EventEndTime
FROM (
SELECT se.SEId, pe.PEId,
COALESCE(pe.StaffName, se.StaffName) AS StaffName, -- << Problem!
COALESCE(pe.EventTime, se.EventTime) AS EventTime,
COALESCE(pe.EventType, se.EventType) AS EventType,
COALESCE(pe.Duration, se.Duration) AS Duration,
COALESCE(pe.Data, se.Data) AS Data,
COALESCE(pe.Field, se.Field) AS Field,
pe.ThisThing, se.OtherThing
FROM PE pe FULL OUTER JOIN SE se
ON pe.StaffName = se.StaffName
AND pe.Duration = se.Duration
AND pe.EventTime = se.EventTime
WHERE NOT(pe.ThisThing = 1 AND se.OtherThing = 0)
) Z
Das rechtfertigt wahrscheinlich nicht den ganzen Grund für die Abfragestruktur, gibt Ihnen aber vielleicht eine Idee - diese Ansicht verbindet zwei sehr schlecht gestaltete Tabellen, über die ich keine Kontrolle habe, und versucht, einige Informationen daraus zu synthetisieren.
Da dies eine Ansicht ist, die von der Anwendung verwendet wird, verpacke ich sie beim Optimieren in ein anderes SELECT wie folgt:
SELECT * FROM (
-- … above code …
) Q
WHERE StaffName = 'SMITH, JOHN Q'
weil die Anwendung im Ergebnis nach bestimmten Mitarbeitern sucht.
Das Problem scheint der COALESCE(pe.StaffName, se.StaffName) AS StaffName
Abschnitt zu sein, den ich aus der Ansicht auf auswähle StaffName
. Wenn ich das auf pe.StaffName AS StaffName
oder ändere, se.StaffName AS StaffName
verschwinden die Leistungsprobleme (siehe jedoch Update 2 unten) . Aber das geht nicht, weil die eine oder andere Seite derFULL OUTER JOIN
fehlen könnte und das eine oder andere Feld möglicherweise NULL ist.
Kann ich das ersetzen, indem ich das ersetze? COALESCE(…)
durch etwas anderes , das in die Unterabfrage umgeschrieben wird?
Weitere Hinweise:
- Ich habe bereits einige Indizes hinzugefügt, um Leistungsprobleme mit dem Rest der Abfrage zu beheben - ohne die
COALESCE
diese ist es sehr schnell. - Zu meiner Überraschung werden beim Betrachten des Ausführungsplans keine Flags ausgelöst, selbst wenn die umschließende Unterabfrage und
WHERE
Anweisung eingeschlossen sind. Meine gesamten Unterabfragekosten im Analysator betragen0.0065736
. Hmph. Die Ausführung dauert vier Sekunden. - Das Ändern der Anwendung auf eine andere Abfrage
(z. B. Zurückgebenfunktioniert möglicherweise, aber als letzter Ausweg hoffe ich wirklich, dass ich die Ansicht optimieren kann, ohne die Anwendung berühren zu müssen.pe.StaffName AS PEStaffName, se.StaffName AS SEStaffName
und AusführenWHERE PEStaffName = 'X' OR SEStaffName = 'X'
) - Eine gespeicherte Prozedur wäre wahrscheinlich sinnvoller, aber die Anwendung wurde mit Entity Framework erstellt, und ich konnte nicht herausfinden, wie sie mit einem SP, der einen Tabellentyp zurückgibt, gut funktioniert (ein ganz anderes Thema).
Indizes
Die Indizes, die ich bisher hinzugefügt habe, sehen ungefähr so aus:
CREATE NONCLUSTERED INDEX [IX_PE_EventTime]
ON [dbo].[PE] ([EventTime])
INCLUDE ([StaffName],[Duration],[EventType],[Data],[Field],[ThisThing])
CREATE NONCLUSTERED INDEX [IX_SE_EventTime]
ON [dbo].[SE] ([EventTime])
INCLUDE ([StaffName],[Duration],[EventType],[Data],[Field],[OtherThing])
Aktualisieren
Hmm ... Ich habe versucht, die betroffene Veränderung oben zu simulieren, und es hat nicht geholfen. Dh vorher habe ) Z
ich hinzugefügtAND (pe.StaffName = 'SMITH, JOHN Q' OR se.StaffName = 'SMITH, JOHN Q')
, aber die Leistung ist die gleiche. Jetzt weiß ich wirklich nicht, wo ich anfangen soll.
Update 2
Durch den Kommentar von @ypercube zur Notwendigkeit des vollständigen Joins wurde mir klar, dass meine synthetisierte Abfrage eine wahrscheinlich wichtige Komponente ausgelassen hat. Während, ja, ich den vollständigen Join benötige, würde der Test, den ich oben durchgeführt habe, indem ich den gelöscht COALESCE
und nur eine Seite des Joins auf einen Wert ungleich Null getestet habe , die andere Seite des vollständigen Joins irrelevant machen , und der Optimierer hat dies wahrscheinlich verwendet Tatsache, um die Abfrage zu beschleunigen. Außerdem habe ich das Beispiel aktualisiert, um zu zeigen, dass StaffName
es sich tatsächlich um einen der Join-Schlüssel handelt - was wahrscheinlich einen erheblichen Einfluss auf die Frage hat. Ich neige jetzt auch zu seinem Vorschlag, dass es die Antwort sein könnte, dies in eine Drei-Wege-Union zu zerlegen, anstatt sich vollständig anzuschließen, und die Fülle von COALESCE
s, die ich sowieso mache, zu vereinfachen . Ich versuche es jetzt.
quelle
KeyField
, beide indiziertINCLUDE
dasStaffName
Feld und mehrere andere Felder. Ich kann die Indexdefinitionen in der Frage posten. Ich arbeite auf einem Testserver daran, damit ich alle Indizes hinzufügen kann, die Sie für hilfreich halten, um es zu versuchen!WHERE pe.ThisThing = 1 AND se.OtherThing = 0
Bedingung, dass derFULL OUTER
Join abgebrochen wird und die Abfrage einem inneren Join entspricht. Sind Sie sicher, dass Sie einen vollständigen Join benötigen?INNER JOIN
,LEFT JOIN
mitWHERE IS NULL
Scheck, RIGHT mit IS NULL JOIN) und dannUNION ALL
die drei Teile. Auf diese Weise ist keine Verwendung erforderlich,COALESCE()
und es kann (möglicherweise) dem Optimierer helfen, das Umschreiben herauszufinden.Antworten:
Dies war ziemlich langwierig, aber da das OP sagt, dass es funktioniert hat, füge ich es als Antwort hinzu (Sie können es gerne korrigieren, wenn Sie etwas falsch finden).
Versuchen Sie, die interne Abfrage in drei Teile zu brechen (
INNER JOIN
,LEFT JOIN
mitWHERE IS NULL
Scheck,RIGHT JOIN
mitIS NULL
Scheck) und dannUNION ALL
den drei Teilen. Dies hat folgende Vorteile:Das Optimierungsprogramm verfügt über weniger Transformationsoptionen für
FULL
Joins als für (die häufigeren)INNER
undLEFT
Joins.Die
Z
abgeleitete Tabelle kann aus der Ansichtsdefinition entfernt werden (Sie können dies trotzdem tun).Das
NOT(pe.ThisThing = 1 AND se.OtherThing = 0)
wird nur für denINNER
Join-Teil benötigt.Geringfügige Verbesserung, die Verwendung
COALESCE()
ist minimal, wenn überhaupt (ich habe angenommen, dassse.SEId
undpe.PEId
nicht nullbar. Wenn mehr Spalten nicht nullbar sind, können Sie mehrCOALESCE()
Aufrufe entfernen .)Wichtiger ist, dass der Optimierer möglicherweise alle Bedingungen in herunterdrückt Ihre Abfragen, die diese Spalten betreffen (jetzt
COALESCE()
wird der Push nicht blockiert.)All dies bietet dem Optimierer mehr Optionen zum Transformieren / Umschreiben von Abfragen, die die Ansicht verwenden, sodass möglicherweise ein Ausführungsplan gefunden wird, der Indizes für die zugrunde liegenden Tabellen verwendet.
Insgesamt kann die Ansicht wie folgt geschrieben werden:
quelle
Meine Intuition wäre, dass dies kein Problem sein sollte, da zu dem Zeitpunkt, an dem dies
COALESCE(pe.StaffName, se.StaffName) AS StaffName
geschieht, alle Zeilen aus den beiden Quellen bereits eingezogen und abgeglichen werden sollten, sodass der Funktionsaufruf ein einfacher speicherinterner Vergleich mit Null und ist -pick. Offensichtlich ist dies nicht der Fall. Vielleicht lässt etwas in einer der Quellen (wenn es sich um Ansichten oder inline abgeleitete Tabellen handelt) oder in den Basistabellen (dh fehlende Indizes) den Abfrageplaner glauben, dass diese Spalten separat gescannt werden müssen.Ohne weitere Einzelheiten zu der genauen Abfrage, die Sie ausführen, den unterstützenden Strukturen und den erstellten Abfrageplänen ist alles, was wir vorschlagen, eine Vermutung.
Um zu versuchen, den Vergleich zu erzwingen, können Sie versuchen, einfach beide Werte in der deribierten Tabelle (
pe.StaffName AS pe.StaffName, se.StaffName AS seStaffName
) auszuwählen und dann die Auswahl in der äußeren Abfrage (COALESCE(peStaffName, seStaffName) AS StaffName
) durchzuführen , oder Sie können sogar die Daten aus der inneren Abfrage in verschieben Eine temporäre Tabelle führt dann die äußere Abfrage durch Auswahl aus (dies würde jedoch eine gespeicherte Prozedur erfordern, und abhängig von der Anzahl der Zeilen könnte dieser Dump-to-Tempdb teuer und daher für sich genommen problematisch sein).quelle
Z
kommt derzeit mit ~ 1,5 m Zeilen zurück. Ich möchte, dass dieses Prädikat in die Abfrage umgeschrieben wird,Z
damit es die Indizes verwendet. Aber jetzt bin ich auch verwirrt, denn wenn ich das Prädikat manuell dort ablege, wird immer noch kein Index verwendet Ich bin mir nicht sicher.