Jeśli TVP są „zauważalnie wolniejsze” niż inne opcje, najprawdopodobniej nie wdrażasz ich poprawnie.
- Nie powinieneś używać DataTable, chyba że Twoja aplikacja ma do tego zastosowanie poza wysyłaniem wartości do TVP. Korzystanie z
IEnumerable<SqlDataRecord>
Interfejs jest szybszy i zużywa mniej pamięci, ponieważ nie duplikujesz kolekcji w pamięci tylko po to, aby wysłać ją do bazy danych. Mam to udokumentowane w następujących miejscach:- Jak mogę wstawić 10 milionów rekordów w jak najkrótszym czasie? (mnóstwo dodatkowych informacji i linków również tutaj)
- Przekaż słownik do T-SQL procedur składowanych
- Przesyłanie strumieniowe danych Do SQL Server 2008 z aplikacji (na SQLServerCentral.com; wymagana bezpłatna rejestracja)
-
Nie powinieneś używać
AddWithValue
dla SqlParameter, chociaż nie jest to prawdopodobnie problem z wydajnością. Ale nadal powinno być:SqlParameter tvp = com.Parameters.Add("data", SqlDbType.Structured); tvp.Value = MethodThatReturnsIEnumerable<SqlDataRecord>(MyCollection);
- TVP są zmiennymi tabelowymi i jako takie nie prowadzą statystyk. Oznacza to, że do Optymalizatora zapytań zgłaszają tylko jeden wiersz. Tak więc w proc, albo:
- Użyj rekompilacji na poziomie instrukcji dla dowolnych zapytań używających TVP do czegokolwiek innego niż prosty SELECT:
OPTION (RECOMPILE)
- Utwórz lokalną tabelę tymczasową (np. pojedynczy
#
) i skopiuj zawartość TVP do tabeli tymczasowej - Możesz spróbować dodać klastrowany klucz podstawowy do typu tabeli zdefiniowanej przez użytkownika
- Jeśli używasz programu SQL Server 2014 lub nowszego, możesz spróbować użyć tabel OLTP w pamięci/zoptymalizowanych pod kątem pamięci. Zobacz:Szybsza tabela temp i zmienna tabeli dzięki optymalizacji pamięci
- Użyj rekompilacji na poziomie instrukcji dla dowolnych zapytań używających TVP do czegokolwiek innego niż prosty SELECT:
Jeśli chodzi o to, dlaczego widzisz:
insert into @data ( ... fields ... ) values ( ... values ... )
-- for each row
insert into @data ( ... fields ... ) values ( ... values ... )
zamiast:
insert into @data ( ... fields ... )
values ( ... values ... ),
( ... values ... ),
JEŚLI tak właśnie się dzieje, to:
- Jeśli wstawki są wykonywane w ramach transakcji, nie ma rzeczywistej różnicy w wydajności
- Nowsza składnia listy wartości (tj.
VALUES (row1), (row2), (row3)
) jest ograniczone do około 1000 wierszy, a zatem nie jest to opłacalna opcja dla TVP, które nie mają tego limitu. JEDNAK prawdopodobnie nie jest to powód, dla którego używane są poszczególne wstawki, biorąc pod uwagę, że nie ma ograniczeń podczas wykonywaniaINSERT INTO @data (fields) SELECT tab.[col] FROM (VALUES (), (), ...) tab([col])
, który udokumentowałem tutaj:Maksymalna liczba wierszy dla konstruktora wartości tabeli . Zamiast tego... - Powodem jest najprawdopodobniej to, że wykonanie pojedynczych wstawek umożliwia przesyłanie strumieniowe wartości z kodu aplikacji do SQL Server:
- za pomocą iteratora (tj.
IEnumerable<SqlDataRecord>
zauważono w punkcie 1 powyżej), kod aplikacji wysyła każdy wiersz, gdy jest on zwracany z metody, i - konstruowanie
VALUES (), (), ...
listy, nawet jeśli robiszINSERT INTO ... SELECT FROM (VALUES ...)
podejście (które nie jest ograniczone do 1000 wierszy), które nadal wymagałoby zbudowania całościVALUES
lista przed wysłaniem dowolnego danych do SQL Server. Jeśli jest dużo danych, zbudowanie bardzo długiego łańcucha zajęłoby więcej czasu i zajęłoby to znacznie więcej pamięci.
- za pomocą iteratora (tj.
Zapoznaj się również z tym dokumentem zespołu doradczego programu SQL Server dla klientów:Maksymalizacja przepustowości dzięki TVP