Wenn TVPs "merklich langsamer" sind als die anderen Optionen, dann implementieren Sie sie höchstwahrscheinlich nicht richtig.
- Sie sollten keine DataTable verwenden, es sei denn, Ihre Anwendung benötigt sie außerhalb des Sendens der Werte an das TVP. Verwenden von
IEnumerable<SqlDataRecord>
Schnittstelle ist schneller und benötigt weniger Speicher, da Sie die Sammlung nicht im Speicher duplizieren, nur um sie an die DB zu senden. Ich habe dies an folgenden Stellen dokumentiert:- Wie kann ich 10 Millionen Datensätze in kürzester Zeit einfügen? (viele zusätzliche Informationen und Links auch hier)
- Wörterbuch an Stored Procedure T-SQL übergeben
- Streaming von Daten In SQL Server 2008 aus einer Anwendung (auf SQLServerCentral.com; kostenlose Registrierung erforderlich)
-
Sie sollten
AddWithValue
nicht verwenden für den SqlParameter, obwohl dies wahrscheinlich kein Leistungsproblem ist. Aber trotzdem sollte es sein:SqlParameter tvp = com.Parameters.Add("data", SqlDbType.Structured); tvp.Value = MethodThatReturnsIEnumerable<SqlDataRecord>(MyCollection);
- TVPs sind Tabellenvariablen und führen als solche keine Statistiken. Das heißt, sie melden dem Abfrageoptimierer, dass sie nur eine Zeile haben. Also, in Ihrer Prozedur, entweder:
- Verwenden Sie die Neukompilierung auf Anweisungsebene bei allen Abfragen, die das TVP für etwas anderes als ein einfaches SELECT verwenden:
OPTION (RECOMPILE)
- Erstellen Sie eine lokale temporäre Tabelle (d. h. einzelne
#
) und kopieren Sie den Inhalt des TVP in die temporäre Tabelle - Sie könnten versuchen, dem benutzerdefinierten Tabellentyp einen gruppierten Primärschlüssel hinzuzufügen
- Wenn Sie SQL Server 2014 oder neuer verwenden, können Sie versuchen, In-Memory OLTP / speicheroptimierte Tabellen zu verwenden. Siehe:Schnellere temporäre Tabelle und Tabellenvariable durch Verwendung der Speicheroptimierung
- Verwenden Sie die Neukompilierung auf Anweisungsebene bei allen Abfragen, die das TVP für etwas anderes als ein einfaches SELECT verwenden:
In Bezug darauf, warum Sie Folgendes sehen:
insert into @data ( ... fields ... ) values ( ... values ... )
-- for each row
insert into @data ( ... fields ... ) values ( ... values ... )
statt:
insert into @data ( ... fields ... )
values ( ... values ... ),
( ... values ... ),
WENN das tatsächlich passiert, dann:
- Wenn die Einfügungen innerhalb einer Transaktion erfolgen, gibt es keinen wirklichen Leistungsunterschied
- Die neuere Wertlistensyntax (d. h.
VALUES (row1), (row2), (row3)
) ist auf etwa 1000 Zeilen begrenzt und daher keine praktikable Option für TVPs, die diese Begrenzung nicht haben. JEDOCH ist dies wahrscheinlich nicht der Grund dafür, dass einzelne Einfügungen verwendet werden, da es keine Begrenzung gibt, wennINSERT INTO @data (fields) SELECT tab.[col] FROM (VALUES (), (), ...) tab([col])
, die ich hier dokumentiert habe:Maximale Zeilenanzahl für den Tabellenwertkonstruktor . Stattdessen... - Der Grund dafür ist höchstwahrscheinlich, dass durch einzelne Einfügungen die Werte aus dem App-Code in SQL Server gestreamt werden können:
- unter Verwendung eines Iterators (d. h. der
IEnumerable<SqlDataRecord>
wie in Nr. 1 oben erwähnt), sendet der App-Code jede Zeile so, wie sie von der Methode zurückgegeben wird, und - Konstruktion der
VALUES (), (), ...
Liste, selbst wenn SieINSERT INTO ... SELECT FROM (VALUES ...)
ausführen Ansatz (der nicht auf 1000 Zeilen beschränkt ist), würde dies immer noch den Aufbau des gesamten erfordernVALUES
Liste vor dem Senden von beliebigen der Daten in SQL Server. Wenn es viele Daten gibt, würde es länger dauern, den superlangen String zu konstruieren, und es würde dabei viel mehr Speicher beanspruchen.
- unter Verwendung eines Iterators (d. h. der
Bitte lesen Sie auch dieses Whitepaper des SQL Server-Kundenberatungsteams:Durchsatzmaximierung mit TVP