Meine Antwort konzentriert sich fast ausschließlich auf SQL Server, nur weil ich eine ziemlich detaillierte Antwort geben werde und nicht über das gleiche Fachwissen auf anderen Plattformen verfüge.
Zunächst ist es wichtig zu wissen, dass das Abfrageoptimierungsprogramm nicht direkt mit dem von Ihnen geschriebenen SQL funktioniert. Es wird vor der Optimierung in ein internes Format umgewandelt. Was Sie für potenzielle Abfrage 1 und Abfrage 2 aufgelistet haben, ist bis auf subtile Unterschiede in der Ansicht ziemlich dasselbe. Eine ähnliche Frage zum Unterschied zwischen Abfrage 1 und Abfrage 2 wurde hier gestellt und beantwortet . Wenn Sie mehr über das interne Format von SQL Server erfahren möchten, können Sie eine hervorragende Reihe von Blog-Posts von Paul White durchgehen . In den meisten Fällen reicht es jedoch aus, nur die Abfragepläne von zwei Abfragen zu vergleichen, von denen Sie vermuten, dass sie auf dieselbe Weise optimiert wurden.
Es gibt verschiedene Möglichkeiten, wie die Verwendung einer Ansicht die Leistung verbessern kann:
Es ist möglich, Ansichten zu definieren, die als physische Strukturen in der Datenbank implementiert sind. In SQL Server werden diese als indizierte Ansichten bezeichnet . In Oracle werden diese als materialisierte Ansichten bezeichnet. In Oracle kann eine Abfrage, die nicht für eine materialisierte Ansicht geschrieben wurde, weiterhin die materialisierte Ansicht verwenden. Weitere Diskussionen liegen außerhalb des Rahmens dieser Antwort.
Manchmal muss dieselbe SQL-Abfrage auf mehreren RDBMS-Plattformen ausgeführt werden. Mit einer Ansicht können wir eine Syntax verwenden, die für jede Plattform eindeutig ist, aber dieselbe Abfrage an die Datenbanken gesendet wird. Ohne eine Ansicht müssen wir möglicherweise benutzerdefinierte Funktionen verwenden, die die Leistung beeinträchtigen können.
Manchmal fügen Leute wirklich cleveren Code in Ansichten ein. Wenn es besser ist als das, was Sie geschrieben haben, können Sie die Leistung mithilfe der Ansicht verbessern.
Im Allgemeinen ist eine Abfrage, die für eine Ansicht geschrieben wurde, genauso effizient oder weniger effizient als eine Abfrage, die direkt für die Basistabellen geschrieben wurde. Dies liegt daran, dass eine Ansichtsdefinition häufig zusätzliche Spalten und Verknüpfungen enthält, die für die spezifische Frage, die eine Abfrage für die Ansicht stellt, möglicherweise nicht erforderlich sind. Um eine gute Leistung bei einer komplexen Sichtweise zu erzielen, hoffen wir, dass drei Dinge passieren:
Spalteneliminierung. Wenn eine Spalte in einer Ansicht dargestellt, aber in der Abfrage nicht für die Ansicht erwähnt wird, sollte der Wert nicht berechnet werden.
Eliminieren Sie die Eliminierung. Wenn eine Tabelle in einer Ansicht verwendet wird, die sicher entfernt werden kann, ohne die Ergebnisse zu ändern, sollte sie entfernt werden. Manchmal kann dies passieren, wenn der Optimierer mehr Informationen hat. Beispielsweise kann ein Fremdschlüssel nicht in der Datenbank deklariert werden. In anderen Fällen deckt die Regel zum Implementieren der Join-Eliminierung möglicherweise ein bestimmtes Szenario nicht ab. Beispielsweise kann in Oracle die Join-Eliminierung für einen mehrspaltigen Join nicht erfolgen, in SQL Server jedoch.
Prädikat Pushdown. Wenn ich der Ansicht einen Filter hinzufüge und die zugrunde liegende Spalte einen Index enthält, sollte ich diesen Index verwenden können. Ich glaube, das ist es, worauf Ihr Beispiel hinweist. Auch ohne Index möchte ich, dass die Filter so weit wie möglich in den Plan aufgenommen werden, um unnötige Arbeit zu vermeiden.
Nach meiner Erfahrung werden diese Regeln vom Abfrageoptimierer ziemlich gut implementiert, was natürlich eine gute Sache ist, aber für SE-Demos kann es schlecht sein. Wenn wir jedoch hinterhältigen Code schreiben, können wir Beispiele erhalten, die zeigen, dass alle oben genannten Optimierungen fehlschlagen. Dies liegt daran, dass die Regeln, die die Optimierungen implementieren, nicht für jedes mögliche Szenario ausgelegt sind.
Zuerst erstelle ich einige einfache Beispieldaten. Die Daten selbst sind nicht so wichtig, aber die Tabellendefinitionen.
DROP TABLE IF EXISTS dbo.BASE_TABLE;
CREATE TABLE dbo.BASE_TABLE (
ID INT NOT NULL,
ID2 INT NOT NULL,
FILLER VARCHAR(50),
CONSTRAINT BASE_TABLE_ID CHECK (ID > 0),
PRIMARY KEY (ID)
);
INSERT INTO dbo.BASE_TABLE WITH (TABLOCK)
SELECT TOP (1000000)
ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
, ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
, REPLICATE('Z', 50)
FROM master..spt_values t1
CROSS JOIN master..spt_values t2;
DROP TABLE IF EXISTS dbo.EXTRA_TABLE;
CREATE TABLE dbo.EXTRA_TABLE (
ID INT NOT NULL,
ID2 INT NOT NULL,
FILLER VARCHAR(50),
PRIMARY KEY (ID, ID2)
);
INSERT INTO dbo.EXTRA_TABLE WITH (TABLOCK)
SELECT TOP (1000000)
ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
, ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
, REPLICATE('Z', 50)
FROM master..spt_values t1
CROSS JOIN master..spt_values t2;
DROP TABLE IF EXISTS dbo.EMPTY_TABLE;
CREATE TABLE dbo.EMPTY_TABLE(
ID INT NOT NULL,
PRIMARY KEY (ID)
);
DROP TABLE IF EXISTS dbo.EMPTY_CCI;
CREATE TABLE dbo.EMPTY_CCI (
ID INT NOT NULL
, INDEX CCI_EMPTY_CCI CLUSTERED COLUMNSTORE
);
GO
CREATE FUNCTION dbo.THE_BEST_FUNCTION () RETURNS INT
WITH SCHEMABINDING
AS
BEGIN
RETURN NULL;
END;
GO
Hier ist meine hinterhältige Ansichtsdefinition:
CREATE VIEW dbo.SNEAKY_VIEW
AS
SELECT
ABS(t.ID) ID
, COUNT(*) OVER (PARTITION BY ABS(t.ID)) CNT
, dbo.THE_BEST_FUNCTION() FUNCTION_VALUE
FROM dbo.BASE_TABLE t
LEFT OUTER JOIN dbo.EXTRA_TABLE e ON CAST(t.ID AS VARCHAR(10)) = CAST(e.ID AS VARCHAR(10)) + '|' + CAST(e.ID2 AS VARCHAR(10))
LEFT OUTER JOIN dbo.EMPTY_CCI ON 1 = 0
LEFT OUTER JOIN dbo.EMPTY_TABLE e2 ON t.ID = e2.ID;
GO
Die Aussicht ist leider ein Chaos. Als menschlicher Optimierer ist es möglich, diese Abfrage erheblich zu vereinfachen. Der Join gegen EXTRA_TABLE
kann eliminiert werden, da wir gegen den vollständigen Primärschlüssel verbinden, sodass sich die Anzahl der Zeilen nicht ändert. Es ist auch nicht möglich, dass Zeilen übereinstimmen, aber es handelt sich um eine äußere Verknüpfung, sodass nichts beseitigt wird. Der Join gegen EMPTY_CCI
kann beseitigt werden, da die Join-Bedingung niemals übereinstimmt. Die Verknüpfung gegen EMPTY_TABLE
kann beseitigt werden, da wir gegen den vollständigen Primärschlüssel beitreten . Die Funktion kehrt auch immer zurück, NULL
sodass dies nicht berücksichtigt werden muss. Wir können dies also vereinfachen:
SELECT
ID
, 1 CNT
, NULL
FROM dbo.BASE_TABLE t;
Wir können es jedoch noch besser machen. ID
ist aufgrund der Einschränkung immer positiv und ID
immer eindeutig, da es sich um den Primärschlüssel handelt. Die COUNT
Fensterfunktion ist also immer 1. Die Abfrage kann folgendermaßen umgeschrieben werden:
SELECT
ID
, 1 CNT
, NULL
FROM dbo.BASE_TABLE t;
Wird das Abfrageoptimierungsprogramm in der Lage sein, eine Abfrage gegenüber der Ansicht zu vereinfachen? Lassen Sie uns das herausfinden, indem wir die Pläne vergleichen. Hier ist der Plan für die einfache Abfrage:
Hier ist der Plan SELECT *
gegen die Aussicht:
Sie sind ziemlich unterschiedlich. Lassen Sie uns weitere Beispielabfragen durchgehen, um Beispiele für die obigen Optimierungen zu sehen, die möglicherweise nicht genau wie erwartet funktionieren.
Erstens sind skalare benutzerdefinierte Funktionen für die Leistung in SQL Server schlecht. Unter anderem zwingen sie die gesamte Abfrage zu einem seriellen Plan. Diese Abfrage ist für einen parallelen Plan berechtigt und ich erhalte einen auf meinem Computer:
SELECT *
FROM (
SELECT
ABS(t.ID) ID
, COUNT(*) OVER (PARTITION BY ABS(t.ID)) CNT
FROM dbo.BASE_TABLE t
LEFT OUTER JOIN dbo.EXTRA_TABLE e ON CAST(t.ID AS VARCHAR(10)) = CAST(e.ID AS VARCHAR(10)) + '|' + CAST(e.ID2 AS VARCHAR(10))
LEFT OUTER JOIN dbo.EMPTY_CCI ON 1 = 0
LEFT OUTER JOIN dbo.EMPTY_TABLE e2 ON t.ID = e2.ID
) derived_table;
Der Plan:
Selbst wenn ich die Spalte nicht basierend auf der benutzerdefinierten Funktion in der Ansicht auswähle, erhalte ich dennoch einen erzwungenen Serienplan:
SELECT ID, CNT
FROM dbo.SNEAKY_VIEW;
Es gibt also einen Unterschied zwischen der Verwendung einer Ansicht und einer abgeleiteten Tabelle. Manchmal funktioniert die Spalteneliminierung nicht auf die gleiche Weise.
Im zweiten Beispiel wirkt sich die EMPTY_CCI
Tabelle nicht auf die Ergebnisse der Abfrage aus. Entfernen Sie sie daher aus der abgeleiteten Tabelle:
SELECT *
FROM (
SELECT
ABS(t.ID) ID
, COUNT(*) OVER (PARTITION BY ABS(t.ID)) CNT
FROM dbo.BASE_TABLE t
LEFT OUTER JOIN dbo.EXTRA_TABLE e ON CAST(t.ID AS VARCHAR(10)) = CAST(e.ID AS VARCHAR(10)) + '|' + CAST(e.ID2 AS VARCHAR(10))
LEFT OUTER JOIN dbo.EMPTY_TABLE e2 ON t.ID = e2.ID
) derived_table;
Hier ist der Abfrageplan:
Es gibt jedoch einen Unterschied im Abfrageplan gegenüber der Ansicht:
SELECT ID, CNT, FUNCTION_VALUE
FROM dbo.SNEAKY_VIEW;
Der Blick ist in der Lage zu verwenden Batch - Modus , die eine besondere Art und Weise ist die Ausführung der Abfrage der Implementierung , wenn gruppiert columns Indizes in einer Abfrage beteiligt ist. Auch wenn die EMPTY_CCI
Tabelle nicht im Plan angezeigt wird, kann die Abfrage weiterhin für den Stapelmodus verwendet werden. Beachten Sie, dass dies EXTRA_TABLE
in beiden Abfragen unnötig abgefragt wird. Dies liegt daran, dass die Join-Bedingung für SQL Server zu kompliziert war, um festzustellen, ob der Join sicher beseitigt werden konnte. Beachten Sie auch, dass die EMPTY_TABLE
Tabelle in keinem der Abfragepläne angezeigt wird. Das Abfrageoptimierungsprogramm konnte es in beiden Abfragen entfernen.
Schauen wir uns für das dritte Beispiel den Prädikat-Pushdown an. Nehmen wir an, ich möchte filtern, um nur Zeilen einzuschließen, in denen ID = 500000
. Wenn ich das etwas direkt außerhalb der Ansicht mache:
SELECT
ABS(t.ID) ID
, COUNT(*) OVER (PARTITION BY ABS(t.ID)) CNT
FROM dbo.BASE_TABLE t
LEFT OUTER JOIN dbo.EXTRA_TABLE e ON CAST(t.ID AS VARCHAR(10)) = CAST(e.ID AS VARCHAR(10)) + '|' + CAST(e.ID2 AS VARCHAR(10))
WHERE ABS(t.ID) = 500000
OPTION (MAXDOP 1);
Ich kann den Index BASE_TABLE.ID
aufgrund der ABS()
Funktion nicht verwenden. Der Filter wird jedoch in den Scan gedrückt, und es wird erwartet, dass nur eine Zeile zurückgegeben wird. Das kann die Leistung verbessern:
Mit dieser Abfrage:
SELECT ID, CNT
FROM dbo.SNEAKY_VIEW
WHERE ID = 500000;
Wir bekommen einen weniger effizienten Plan. Der Filter on ID = 500000
wird ganz am Ende des Plans implementiert, sodass die Fensterfunktion anhand von fast einer Million unnötiger Zeilen bewertet wird:
Das war vielleicht ein bisschen tiefer als das, wonach Sie gesucht haben. Wenn ich zu Ihrer ursprünglichen Frage zurückkehre, würde ich sagen, dass die Abfrage gegen die Ansicht der potenziellen Abfrage 1 am ähnlichsten ist. Ich habe Gerüchte über einige Fälle gehört, in denen dies nicht der Fall ist. Wenn Sie beispielsweise viele verschachtelte Ansichten haben, kann das Abfrageoptimierungsprogramm möglicherweise Schwierigkeiten haben, diese zu entpacken, und Sie können allein aus diesem Grund eine schlecht optimierte Abfrage erhalten. Ich weiß nicht, ob dies in einer aktuellen Version von SQL Server zutrifft, aber es lohnt sich, dies einfach zu vermeiden, damit andere Personen, die versuchen, Ihren Code zu verstehen, es leichter haben.