Fensterfunktionen verursachen einen schrecklichen Ausführungsplan, wenn sie aus einer Ansicht mit einer externen parametrisierten 'where'-Klausel aufgerufen werden

10

Ich hatte dieses Problem vor langer Zeit, fand eine Problemumgehung, die zu mir passte, und vergaß es.

Aber jetzt gibt es diese Frage zu SO, also bin ich bereit, dieses Problem anzusprechen.

Es gibt eine Ansicht, die einige Tabellen auf sehr einfache Weise verbindet (Bestellungen + Bestellpositionen).

Bei einer Abfrage ohne whereKlausel gibt die Ansicht mehrere Millionen Zeilen zurück.
Niemand nennt es jedoch jemals so. Die übliche Abfrage ist

select * from that_nasty_view where order_number = 123456;

Dies gibt ungefähr 10 Datensätze von 5 m zurück.

Wichtig: Die Ansicht enthält eine Fensterfunktion rank(), die genau nach dem Feld unterteilt ist, mit dem die Ansicht immer abgefragt wird:

rank() over (partition by order_number order by detail_line_number)

Wenn diese Ansicht nun genau wie oben gezeigt mit Literalparametern in der Abfragezeichenfolge abgefragt wird, werden die Zeilen sofort zurückgegeben. Der Ausführungsplan ist in Ordnung:

  • Indexsuche für beide Tabellen unter Verwendung der Indizes auf order_number(gibt 10 Zeilen zurück).
  • Berechnen von Fenstern über dem zurückgegebenen winzigen Ergebnis.
  • Auswählen.

Wenn die Ansicht jedoch parametrisiert aufgerufen wird, werden die Dinge unangenehm:

  • Index scanauf allen Tabellen ohne Indizes. Gibt 5 m Zeilen zurück.
  • Riesige Verbindung.
  • Berechnen von Fenstern über alle partitions (ca. 500.000 Fenster).
  • Filter 10 Reihen aus 5m nehmen.
  • Wählen

Dies geschieht in allen Fällen, wenn Parameter beteiligt sind. Es kann SSMS sein:

declare @order_number int = 123456;
select * from that_nasty_view where order_number = @order_number;

Es kann sich um einen ODBC-Client wie Excel handeln:

select * from that_nasty_view where order_number = ?

Oder es kann ein anderer Client sein, der Parameter und keine SQL-Verkettung verwendet.

Wenn die Fensterfunktion aus der Ansicht entfernt wird, wird sie einwandfrei ausgeführt, unabhängig davon, ob sie mit Parametern abgefragt wird oder nicht.

Meine Problemumgehung bestand darin, die fehlerhafte Funktion zu entfernen und zu einem späteren Zeitpunkt erneut anzuwenden.

Aber was gibt es? Ist es wirklich ein Fehler, wie SQL Server 2008 mit Fensterfunktionen umgeht?

GSerg
quelle
Bestellnummer ist Primärschlüssel? Datentypen von Spalte und Parameter stimmen überein?
Gbn
order_numberist kein Primärschlüssel. int not nullIn beiden Tabellen befindet sich ein nicht gruppierter Index.
GSerg
5
SQL Server 2005 hatte Probleme mit dem Prädikat-Pushing in diesem Bereich. Ich dachte, sie wären jetzt repariert. Übrigens verwendet Ihr TSQL-Beispiel eine Variable, keinen Parameter. Hilft das Hinzufügen OPTION (RECOMPILE)?
Martin Smith
1
@GSerg - Also hat der schlechte Plan mit dem Filter zuletzt geschätzte 5 Millionen Zeilen im Filter und geschätzte 10 Zeilen, die mit dem tatsächlichen übereinstimmen? Wenn ja, ist es vielleicht so, dass das Prädikat-Pushing-Problem immer noch nicht vollständig behoben ist.
Martin Smith

Antworten:

5

Dies scheint ein langjähriges Problem zu sein, das in der einen oder anderen Form immer wieder auftaucht und in SQL Server 2012 immer noch vorhanden ist.

Einige Beiträge, die darüber diskutieren, sind

Alle aktuellen Versionen von SQL Server bis einschließlich 2012 können den Filter für eine Partitionierungsgruppe nicht über das Sequenzprojekt hinaus für ein parametrisiertes Prädikat verschieben, es sei denn, es option(recompile)wird verwendet (falls 2008+).

Eine Alternative zum recompileHinweis wäre, die Abfrage neu zu schreiben, um eine parametrisierte Inline-TVF zu verwenden, wie von @ a1ex07 vorgeschlagen.

Martin Smith
quelle
Hatte gerade den Fall auch in SQL Server 2014
Guillaume86
3

Ich würde versuchen, die Ansicht durch udf mit Tabellenwert zu ersetzen. Auf diese Weise werden zuerst Datensätze gefiltert und dann die Fensterfunktion angewendet. Diese Funktion kann Tabellenparameter akzeptieren , so dass Sie mehrere passieren kann order_numberhinein

a1ex07
quelle
Noch eine Problemumgehung, ja. Das konnte ich allerdings nicht tun, da nicht alle Clients eine Tabellenwertfunktion nutzen konnten.
GSerg
Warum? Ich bin nicht 100% sicher, aber ich denke, alles, was Sie brauchen, ist, die Abfrage ein wenig in etwas wieSELECT * FROM my_funct(12345)
a1ex07
Eine der Anforderungen war, dass die Abfrage von Endbenutzern verwendet werden kann, die Excel (
dh
it will filter records first, and then apply window functionist falsch. Es gibt keine deterministische Reihenfolge für die Hinrichtung
Remus Rusanu