Sqlserver
 sql >> Datenbank >  >> RDS >> Sqlserver

Probleme mit der Leistung von Tabellenwertparametern

Wenn TVPs "merklich langsamer" sind als die anderen Optionen, dann implementieren Sie sie höchstwahrscheinlich nicht richtig.

  1. 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:
  2. 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);
    
  3. 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

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, wenn INSERT 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:
    1. 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
    2. Konstruktion der VALUES (), (), ... Liste, selbst wenn Sie INSERT INTO ... SELECT FROM (VALUES ...) ausführen Ansatz (der nicht auf 1000 Zeilen beschränkt ist), würde dies immer noch den Aufbau des gesamten erfordern VALUES 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.

Bitte lesen Sie auch dieses Whitepaper des SQL Server-Kundenberatungsteams:Durchsatzmaximierung mit TVP