In diesem Artikel werden wir uns mit dem Thema Performance von Tabellenvariablen befassen. In SQL Server können wir Variablen erstellen, die als vollständige Tabellen funktionieren. Vielleicht haben andere Datenbanken die gleichen Fähigkeiten, aber ich habe solche Variablen nur in MS SQL Server verwendet.
Sie können also Folgendes schreiben:
declare @t as table (int value)
Hier deklarieren wir die Variable @t als Tabelle, die eine einzelne Value-Spalte vom Typ Integer enthält. Es ist möglich, komplexere Tabellen zu erstellen, in unserem Beispiel reicht jedoch eine Spalte aus, um die Optimierung zu untersuchen.
Jetzt können wir diese Variable in unseren Abfragen verwenden. Wir können viele Daten hinzufügen und den Datenabruf von dieser Variablen durchführen:
insert into @t select UserID from User or select * from @t
Mir ist aufgefallen, dass Tabellenvariablen verwendet werden, wenn Daten für eine große Auswahl abgerufen werden müssen. Beispielsweise enthält der Code eine Abfrage, die Benutzer der Website zurückgibt. Jetzt sammeln Sie die IDs aller Benutzer, fügen sie der Tabellenvariablen hinzu und können Adressen nach diesen Benutzern suchen. Vielleicht mag jemand fragen, warum wir nicht eine Abfrage auf der Datenbank ausführen und sofort alles bekommen? Ich habe ein einfaches Beispiel.
Angenommen, Benutzer kommen vom Webdienst, während ihre Adressen in Ihrer Datenbank gespeichert sind. In diesem Fall gibt es keinen Ausweg. Wir haben eine Reihe von Benutzer-IDs vom Dienst erhalten, und um eine Abfrage der Datenbank zu vermeiden, entscheidet jemand, dass es einfacher ist, alle IDs als Tabellenvariable zum Abfrageparameter hinzuzufügen, und die Abfrage sieht ordentlich aus:
select * from @t as users join Address a on a.UserID = users.UserID os
Das alles funktioniert korrekt. Im C#-Code können Sie die Ergebnisse beider Datenarrays mithilfe von LINQ schnell in einem Objekt kombinieren. Die Leistung der Abfrage kann jedoch darunter leiden.
Tatsache ist, dass Tabellenvariablen nicht für die Verarbeitung großer Datenmengen konzipiert wurden. Wenn ich mich nicht irre, verwendet der Abfrageoptimierer immer die LOOP-Ausführungsmethode. Daher wird für jede ID von @t eine Suche in der Adresstabelle durchgeführt. Wenn es 1000 Datensätze in @t gibt, scannt der Server die Adresse 1000 Mal.
In Bezug auf die Ausführung bricht der Server aufgrund der wahnsinnigen Anzahl von Scans einfach ab, wenn er versucht, Daten zu finden.
Es ist viel effektiver, die gesamte Adresstabelle zu scannen und alle Benutzer auf einmal zu finden. Diese Methode heißt MERGE. SQL Server wählt es jedoch aus, wenn viele sortierte Daten vorhanden sind. In diesem Fall weiß der Optimierer nicht, wie viele und welche Daten der Variablen hinzugefügt werden und ob eine Sortierung erfolgt, da eine solche Variable keine Indizes enthält.
Wenn die Tabellenvariable wenig Daten enthält und Sie nicht Tausende von Zeilen darin einfügen, ist alles in Ordnung. Wenn Sie jedoch solche Variablen verwenden und ihnen eine große Datenmenge hinzufügen möchten, müssen Sie weiterlesen.
Selbst wenn Sie die Tabellenvariable durch SQL ersetzen, wird die Abfrageleistung erheblich beschleunigt:
select * from ( Select 10377 as UserID Union all Select 73736 Union all Select 7474748 …. ) as users join Address a on a.UserID = users.UserID
Es kann tausend solcher SELECT-Anweisungen geben und der Abfragetext wird riesig sein, aber er wird für eine große Datenmenge tausendmal schneller ausgeführt, da SQL Server einen effektiven Ausführungsplan auswählen kann.
Diese Abfrage sieht nicht gut aus. Sein Ausführungsplan kann jedoch nicht zwischengespeichert werden, da das Ändern nur einer ID auch den gesamten Abfragetext ändert und Parameter nicht verwendet werden können.
Ich denke, Microsoft hat nicht erwartet, dass Benutzer tabellarische Variablen auf diese Weise verwenden, aber es gibt einen netten Workaround.
Es gibt mehrere Möglichkeiten, dieses Problem zu lösen. Meiner Meinung nach ist es jedoch in Bezug auf die Leistung am effektivsten, OPTION (RECOMPILE) am Ende der Abfrage hinzuzufügen:
select * from @t as users join Address a on a.UserID = users.UserID OPTION (RECOMPILE)
Diese Option wird einmal ganz am Ende der Abfrage nach sogar ORDER BY hinzugefügt. Der Zweck dieser Option besteht darin, SQL Server dazu zu bringen, die Abfrage bei jeder Ausführung neu zu kompilieren.
Wenn wir danach die Abfrageleistung messen, wird die Zeit für die Durchführung der Suche höchstwahrscheinlich reduziert. Bei großen Datenmengen kann die Leistungsverbesserung erheblich sein, von mehreren zehn Minuten bis zu Sekunden. Jetzt kompiliert der Server seinen Code, bevor er jede Abfrage ausführt, und verwendet nicht den Ausführungsplan aus dem Cache, sondern generiert einen neuen, abhängig von der Datenmenge in der Variablen, und das hilft normalerweise sehr.
Der Nachteil besteht darin, dass der Ausführungsplan nicht gespeichert wird und der Server die Abfrage kompilieren und jedes Mal nach einem effektiven Ausführungsplan suchen muss. Ich habe jedoch keine Abfragen gesehen, bei denen dieser Vorgang länger als 100 ms gedauert hat.
Ist es eine schlechte Idee, Tabellenvariablen zu verwenden? Nein ist es nicht. Denken Sie nur daran, dass sie nicht für große Datenmengen erstellt wurden. Manchmal ist es besser, eine temporäre Tabelle zu erstellen, wenn viele Daten vorhanden sind, und Daten in diese Tabelle einzufügen oder sogar spontan einen Index zu erstellen. Ich musste dies mit Berichten tun, allerdings nur einmal. Damals habe ich die Zeit für die Erstellung eines Berichts von 3 Stunden auf 20 Minuten reduziert.
Ich bevorzuge die Verwendung einer großen Abfrage, anstatt sie in mehrere Abfragen aufzuteilen und die Ergebnisse in Variablen zu speichern. Erlauben Sie SQL Server, die Leistung einer großen Abfrage zu optimieren, und Sie werden nicht im Stich gelassen. Bitte beachten Sie, dass Sie nur im Extremfall auf Tabellenvariablen zurückgreifen sollten, wenn Sie deren Nutzen wirklich sehen.