Wydajność bcp / BULK INSERT a parametry wartościowane w tabeli

84

Za chwilę będę musiał przepisać jakiś dość stary kod BULK INSERTpoleceniem SQL Servera bo schemat się zmienił i przyszło mi do głowy, że może powinienem pomyśleć o przejściu na procedurę składowaną z TVP, ale zastanawiam się jaki efekt to może mieć na wydajność.

Kilka podstawowych informacji, które mogą pomóc wyjaśnić, dlaczego zadaję to pytanie:

  • Dane faktycznie są dostarczane za pośrednictwem usługi internetowej. Usługa sieciowa zapisuje plik tekstowy w folderze udostępnionym na serwerze bazy danych, który z kolei wykonuje operację BULK INSERT. Ten proces został pierwotnie zaimplementowany w SQL Server 2000 i wtedy tak naprawdę nie było innej alternatywy niż wrzucenie INSERTdo serwera kilkuset instrukcji, co w rzeczywistości było pierwotnym procesem i spowodowało katastrofę wydajności.

  • Dane są zbiorczo wstawiane do stałej tabeli pomostowej, a następnie łączone w znacznie większą tabelę (po czym są usuwane z tabeli pomostowej).

  • Ilość danych do wstawienia jest „duża”, ale nie „ogromna” - zwykle kilkaset wierszy, w rzadkich przypadkach może nawet 5-10 tys. Dlatego mam przeczucie, BULK INSERTże niezalogowana operacja nie zrobi tak dużej różnicy (ale oczywiście nie jestem pewien, stąd pytanie).

  • Wprowadzanie jest w rzeczywistości częścią znacznie większego procesu wsadowego opartego na potokach i musi następować wiele razy z rzędu; dlatego wydajność jest krytyczna.

Powody, dla których chciałbym zastąpić BULK INSERTTVP to:

  • Pisanie pliku tekstowego przez NetBIOS prawdopodobnie już kosztuje trochę czasu i jest dość makabryczne z architektonicznego punktu widzenia.

  • Uważam, że stół pomostowy można (i należy) wyeliminować. Głównym powodem jest to, że wstawione dane muszą zostać użyte do kilku innych aktualizacji w tym samym czasie wstawiania, a próba aktualizacji z ogromnej tabeli produkcyjnej jest o wiele bardziej kosztowna niż użycie prawie pustego przemieszczania stół. W przypadku TVP parametrem w zasadzie jest stół pomostowy, mogę z nim zrobić wszystko, co chcę, przed / po głównej wkładce.

  • Mogłem prawie całkowicie pozbyć się sprawdzania dupleksu, czyszczenia kodu i całego narzutu związanego z wstawianiem zbiorczym.

  • Nie musisz martwić się rywalizacją o blokady w tabeli pomostowej lub tempdb, jeśli serwer pobierze kilka takich transakcji naraz (staramy się tego uniknąć, ale zdarza się).

Oczywiście zamierzam to sprofilować przed wprowadzeniem czegokolwiek do produkcji, ale pomyślałem, że dobrym pomysłem byłoby najpierw zapytać o to, zanim spędzę cały ten czas, sprawdzić, czy ktoś ma jakieś poważne ostrzeżenia dotyczące używania TVP do tego celu.

Zatem - jaki jest werdykt dla każdego, kto jest wystarczająco wygodny w SQL Server 2008, aby spróbować lub przynajmniej zbadać tę sprawę? W przypadku wstawek, powiedzmy kilkuset do kilku tysięcy rzędów, zdarzających się dość często, czy TVP tną musztardę? Czy istnieje znacząca różnica w wydajności w porównaniu z wkładkami zbiorczymi?


Aktualizacja: teraz z 92% mniej znaków zapytania!

(Inna nazwa: Wyniki testu)

Końcowy wynik jest teraz w produkcji po 36-stopniowym procesie wdrażania. Oba rozwiązania zostały dokładnie przetestowane:

  • Wyrywanie kodu folderu współdzielonego i SqlBulkCopybezpośrednie używanie klasy;
  • Przejście do procedury składowanej z TVP.

Aby czytelnicy mogli zorientować się, co dokładnie zostało przetestowane, aby rozwiać wszelkie wątpliwości co do wiarygodności tych danych, oto bardziej szczegółowe wyjaśnienie tego, co faktycznie robi ten proces importu :

  1. Zacznij od tymczasowej sekwencji danych, która zwykle składa się z około 20-50 punktów danych (chociaż czasami może dochodzić do kilkuset);

  2. Wykonaj na nim całą masę szalonych procesów, które są w większości niezależne od bazy danych. Ten proces jest równoległy, więc około 8-10 sekwencji w (1) jest przetwarzanych w tym samym czasie. Każdy proces równoległy generuje 3 dodatkowe sekwencje.

  3. Weź wszystkie 3 sekwencje i oryginalną sekwencję i połącz je w partię.

  4. Połącz partie ze wszystkich 8-10 zakończonych już zadań przetwarzania w jedną dużą super partię.

  5. Zaimportuj go, korzystając ze BULK INSERTstrategii (patrz następny krok) lub strategii TVP (przejdź do kroku 8).

  6. Użyj SqlBulkCopyklasy, aby zrzucić całą super partię do 4 stałych tabel pomostowych.

  7. Uruchom procedurę składowaną, która (a) wykonuje kilka kroków agregacji na 2 tabelach, w tym kilka JOINwarunków, a następnie (b) wykonuje operację MERGEna 6 tabelach produkcyjnych, używając zarówno danych zagregowanych, jak i niezagregowanych. (Skończone)

    LUB

  8. Wygeneruj 4 DataTableobiekty zawierające dane do scalenia; 3 z nich zawierają typy CLR, które niestety nie są odpowiednio obsługiwane przez ADO.NET TVP, więc muszą być wstawione jako reprezentacje ciągów, co nieco obniża wydajność.

  9. Przekaż TVP do procedury składowanej, która zasadniczo przetwarza to samo co (7), ale bezpośrednio z otrzymanymi tabelami. (Skończone)

Wyniki były dość zbliżone, ale podejście TVP ostatecznie wypadło średnio lepiej, nawet jeśli dane nieznacznie przekraczały 1000 wierszy.

Zwróć uwagę, że ten proces importu jest uruchamiany kolejno wiele tysięcy razy, więc bardzo łatwo było uzyskać średni czas po prostu zliczając, ile godzin (tak, godzin) zajęło zakończenie wszystkich połączeń.

Początkowo średnie scalanie trwało prawie dokładnie 8 sekund (przy normalnym obciążeniu). Usunięcie kludge NetBIOS i przejście na SqlBulkCopyskróciło czas do prawie dokładnie 7 sekund. Przejście na TVP dodatkowo skróciło czas do 5,2 sekundy na partię. To 35% wzrost przepustowości procesu, którego czas pracy mierzy się w godzinach - więc wcale nie jest zły. To także ~ 25% poprawa w stosunku do SqlBulkCopy.

Jestem właściwie przekonany, że prawdziwa poprawa była znacznie większa. Podczas testów okazało się, że ostateczne scalenie nie jest już ścieżką krytyczną; zamiast tego usługa sieciowa, która zajmowała się całym przetwarzaniem danych, zaczynała się rozpadać pod liczbą napływających żądań. Ani procesor, ani baza danych we / wy nie były tak naprawdę wyczerpane i nie było znaczącej aktywności blokującej. W niektórych przypadkach widzieliśmy przerwę kilku sekund bezczynności między kolejnymi połączeniami. Wystąpiła niewielka przerwa, ale znacznie mniejsza (około pół sekundy) podczas używania SqlBulkCopy. Ale przypuszczam, że stanie się to opowieścią na inny dzień.

Wniosek: Parametry wyceniane w tabeli naprawdę działają lepiej niż BULK INSERToperacje dla złożonych procesów importu i transformacji działających na średnich zbiorach danych.


Chciałbym dodać jeszcze jedną kwestię, żeby złagodzić obawy ze strony ludzi, którzy są za stołami pro-staging. W pewnym sensie cała ta usługa jest jednym gigantycznym procesem przejściowym. Każdy etap procesu jest poddawany szczegółowej kontroli, więc nie potrzebujemy tabeli pomostowej, aby określić, dlaczego jakieś konkretne scalanie nie powiodło się (chociaż w praktyce prawie nigdy się to nie zdarza). Wszystko, co musimy zrobić, to ustawić flagę debugowania w usłudze, która zepsuje debuger lub zrzuci swoje dane do pliku zamiast bazy danych.

Innymi słowy, mamy już więcej niż wystarczający wgląd w proces i nie potrzebujemy bezpieczeństwa tabeli pomostowej; jedynym powodem, dla którego mieliśmy stół pomostowy na pierwszym miejscu, było uniknięcie rzucania się na wszystkie stwierdzenia INSERTi UPDATE, których musielibyśmy użyć w przeciwnym razie. W pierwotnym procesie dane przemieszczania i tak znajdowały się w tabeli pomostowej tylko przez ułamki sekundy, więc nie dodały żadnej wartości pod względem konserwacji / utrzymania.

Pamiętaj również, że nie zastąpiliśmy każdej BULK INSERToperacji TVP. Kilka operacji, które zajmują się większymi ilościami danych i / lub nie muszą robić nic specjalnego z danymi poza wyrzuceniem ich do bazy danych, nadal są używane SqlBulkCopy. Nie sugeruję, że TVP to panaceum na wydajność, tylko że udało im się SqlBulkCopyto w tym konkretnym przypadku, obejmującym kilka transformacji między początkową inscenizacją a ostatecznym połączeniem.

Więc masz to. Wskazuje na TToni w celu znalezienia najbardziej odpowiedniego linku, ale doceniam również inne odpowiedzi. Dzięki jeszcze raz!

Aaronaught
źródło
To niesamowite pytanie samo w sobie, czuję, że część aktualizacyjna powinna być w odpowiedzi;)
Marc.2377

Odpowiedzi:

10

I tak naprawdę nie mają jeszcze doświadczenia z TVP, jednak nie jest to miłe porównanie wydajności wykres vs. BULK INSERT w MSDN tutaj .

Mówią, że BULK INSERT ma wyższy koszt początkowy, ale później jest szybszy. W scenariuszu z klientem zdalnym rysują linię na około 1000 wierszy (dla „prostej” logiki serwera). Sądząc po ich opisie, powiedziałbym, że korzystanie z TVP powinno być w porządku. Uderzenie w wydajność - jeśli w ogóle - jest prawdopodobnie znikome, a korzyści architektoniczne wydają się bardzo dobre.

Edycja: na marginesie można uniknąć pliku lokalnego na serwerze i nadal używać kopii zbiorczej przy użyciu obiektu SqlBulkCopy. Wystarczy wypełnić DataTable i wprowadzić ją do metody „WriteToServer” instancji SqlBulkCopy. Łatwy w użyciu i bardzo szybki.

TToni
źródło
Dzięki za link, jest to całkiem przydatne, ponieważ MS wydaje się polecać TVP, gdy dane dostarczają złożoną logikę (co robi), a my mamy również możliwość zwiększenia lub zmniejszenia rozmiaru partii, aby nie przekraczać zbytnio Punkt bólu w 1 tys.rzędu. W związku z tym warto poświęcić trochę czasu, aby przynajmniej spróbować i zobaczyć, nawet jeśli skończy się to zbyt wolno.
Aaronaught
Tak, link jest interesujący. @Aaronaught - w takich sytuacjach zawsze warto zbadać i przeanalizować skuteczność potencjalnych podejść, dlatego chciałbym poznać Twoje ustalenia!
AdaTheDev
7

Wykres wspomniany w związku z linkiem podanym w odpowiedzi @ TToni należy traktować w kontekście. Nie jestem pewien, ile faktycznych badań poświęcono tym zaleceniom (zwróć także uwagę, że wykres wydaje się być dostępny tylko w wersjach 2008i 2008 R2tej dokumentacji).

Z drugiej strony jest to oficjalne opracowanie zespołu doradztwa dla klientów SQL Server: Maksymalizacja przepustowości z TVP

Używam TVP od 2009 roku i stwierdziłem, przynajmniej z mojego doświadczenia, że ​​dla czegokolwiek innego niż proste wstawienie do tabeli docelowej bez dodatkowych potrzeb logicznych (co rzadko się zdarza), TVP są zazwyczaj lepszą opcją.

Zwykle unikam tabel pomostowych, ponieważ sprawdzanie poprawności danych powinno być wykonywane w warstwie aplikacji. Korzystając z TVP, jest to łatwe do dostosowania, a zmienna tabeli TVP w procedurze składowanej jest z natury zlokalizowaną tabelą pomostową (stąd nie ma konfliktu z innymi procesami działającymi w tym samym czasie, jak w przypadku używania prawdziwej tabeli do przemieszczania) ).

Jeśli chodzi o testy przeprowadzone w pytaniu, myślę, że można je wykazać jeszcze szybciej niż pierwotnie stwierdzono:

  1. Nie powinieneś używać DataTable, chyba że Twoja aplikacja ma do tego zastosowanie poza wysyłaniem wartości do TVP. Korzystanie z IEnumerable<SqlDataRecord>interfejsu jest szybsze i zużywa mniej pamięci, ponieważ nie kopiujesz kolekcji w pamięci tylko po to, aby wysłać ją do bazy danych. Mam to udokumentowane w następujących miejscach:
  2. TVP są zmiennymi tabelarycznymi i jako takie nie prowadzą statystyk. Oznacza to, że zgłaszają tylko 1 wiersz do Optymalizatora zapytań. Więc w swoim procie:
    • Użyj rekompilacji na poziomie instrukcji dla wszystkich zapytań używających TVP do czegokolwiek innego niż prosty SELECT: OPTION (RECOMPILE)
    • Utwórz lokalną tabelę tymczasową (tj. Pojedynczą #) i skopiuj zawartość TVP do tabeli tymczasowej
Solomon Rutzky
źródło
4

Myślę, że nadal bym się trzymał podejścia zbiorczego. Może się okazać, że tempdb nadal jest trafiany za pomocą TVP z rozsądną liczbą wierszy. To jest moje przeczucie, nie mogę powiedzieć, że przetestowałem działanie TVP (chociaż jestem zainteresowany również słuchaniem opinii innych)

Nie wspominasz o tym, czy używasz .NET, ale podejście, które podjąłem do optymalizacji poprzednich rozwiązań, polegało na masowym ładowaniu danych za pomocą klasy SqlBulkCopy - nie musisz wcześniej zapisywać danych do pliku ładowanie, po prostu nadaj klasie SqlBulkCopy (np.) DataTable - to najszybszy sposób na wstawienie danych do bazy danych. 5–10 tys. Wierszy to niewiele. Użyłem tego do 750 tys. Wierszy. Podejrzewam, że generalnie przy kilkuset rzędach nie zrobiłoby to dużej różnicy przy użyciu TVP. Ale skalowanie w górę byłoby ograniczone IMHO.

Być może nowa funkcja MERGE w SQL 2008 przyniosłaby korzyści?

Ponadto, jeśli istniejąca tabela pomostowa jest pojedynczą tabelą używaną dla każdego wystąpienia tego procesu i martwisz się rywalizacją itp., Czy rozważałeś utworzenie nowej „tymczasowej”, ale fizycznej tabeli pomostowej za każdym razem, a następnie porzucenie jej, gdy jest Skończyć z?

Zauważ, że możesz zoptymalizować ładowanie do tej tabeli pomostowej, wypełniając ją bez żadnych indeksów. Następnie po wypełnieniu dodaj wszystkie wymagane indeksy w tym punkcie (FILLFACTOR = 100 dla optymalnej wydajności odczytu, ponieważ w tym momencie nie będzie aktualizowany).

AdaTheDev
źródło
Używam .NET, a ten proces jest starszy SqlBulkCopyi po prostu nigdy nie został zmieniony. Dzięki za przypomnienie mi o tym, może warto do niego wrócić. MERGEjest już szeroko używany, a tymczasowe tabele były już kiedyś wypróbowywane, ale okazało się, że są wolniejsze i trudniejsze w zarządzaniu. Dzięki za wkład!
Aaronaught
-2

Stoły pomostowe są dobre! Naprawdę nie chciałbym tego robić w żaden inny sposób. Czemu? Ponieważ import danych może się nieoczekiwanie zmienić (i często w sposób, którego nie można przewidzieć, np. Kiedy kolumny były nadal nazywane imieniem i nazwiskiem, ale miały dane o imieniu w kolumnie z nazwiskiem, na przykład po to, aby wybrać przykład nie losowo). Łatwo zbadać problem dzięki tabeli pomostowej, dzięki czemu można dokładnie zobaczyć, jakie dane znajdowały się w kolumnach obsługiwanych przez import. Myślę, że trudniej znaleźć, kiedy używasz tabeli w pamięci. Znam wielu ludzi, którzy tak jak ja zajmują się importem i wszyscy zalecają używanie tabel pomostowych. Podejrzewam, że jest ku temu powód.

Dalsze naprawianie niewielkiej zmiany schematu w procesie roboczym jest łatwiejsze i mniej czasochłonne niż przeprojektowanie procesu. Jeśli działa i nikt nie jest skłonny płacić godzinami, aby go zmienić, napraw tylko to, co należy naprawić z powodu zmiany schematu. Zmieniając cały proces, wprowadzasz znacznie więcej potencjalnych nowych błędów niż wprowadzając niewielką zmianę w istniejącym, przetestowanym procesie roboczym.

I jak zamierzasz pozbyć się wszystkich zadań związanych z czyszczeniem danych? Możesz robić to inaczej, ale nadal trzeba to zrobić. Ponownie, zmiana procesu tak, jak opisujesz, jest bardzo ryzykowna.

Osobiście wydaje mi się, że jesteś po prostu urażony, używając starszych technik, zamiast mieć szansę bawić się nowymi zabawkami. Wydaje się, że nie masz prawdziwych podstaw do chęci zmiany innych niż wstawianie zbiorcze, więc 2000.

HLGEM
źródło
27
SQL 2008 istnieje już od 2 lat, a ten proces istnieje już od wieków i po raz pierwszy rozważałem jego zmianę. Czy złośliwy komentarz na końcu był naprawdę potrzebny?
Aaronaught