Dzięki SourceTable
posiadaniu> 15 MM rekordów i Bad_Phrase
> 3K rekordów uruchomienie następującego zapytania w programie SQL Server 2005 SP4 zajmuje prawie 10 godzin.
UPDATE [SourceTable]
SET
Bad_Count=
(
SELECT
COUNT(*)
FROM Bad_Phrase
WHERE
[SourceTable].Name like '%'+Bad_Phrase.PHRASE+'%'
)
W języku angielskim to zapytanie liczy liczbę odrębnych fraz wymienionych w Bad_Phrase, które są podciągiem pola Name
w polu, SourceTable
a następnie umieszcza ten wynik w polu Bad_Count
.
Chciałbym uzyskać kilka sugestii, w jaki sposób uruchomić to zapytanie znacznie szybciej.
sql-server
sql-server-2005
update
subquery
Matthew Lehrer
źródło
źródło
Odpowiedzi:
Chociaż zgadzam się z innymi komentatorami, że jest to problem kosztowny obliczeniowo, myślę, że jest wiele miejsca na ulepszenia poprzez ulepszenie używanego SQL. Aby to zilustrować, tworzę fałszywy zestaw danych z nazwami 15MM i zwrotami 3K, zastosowałem stare podejście i uruchomiłem nowe podejście.
Pełny skrypt do wygenerowania fałszywego zestawu danych i wypróbowania nowego podejścia
TL; DR
Na moim komputerze i tym fałszywym zestawie danych uruchomienie oryginalnego podejścia zajmuje około 4 godzin . Proponowane nowe podejście zajmuje około 10 minut , co stanowi znaczną poprawę. Oto krótkie podsumowanie proponowanego podejścia:
Oryginalne podejście: analiza algorytmiczna
Z planu pierwotnego
UPDATE
stwierdzenia wynika, że ilość pracy jest liniowo proporcjonalna zarówno do liczby nazw (15 MM), jak i do liczby fraz (3K). Jeśli więc pomnożymy zarówno liczbę nazwisk, jak i wyrażeń przez 10, całkowity czas działania będzie ~ 100 razy wolniejszy.Zapytanie jest również proporcjonalne do długości
name
; choć jest to nieco ukryte w planie zapytań, pojawia się w „liczbie wykonań” wyszukiwania w buforze tabeli. W obecnym planie widzimy, że dzieje się to nie tylko raz naname
, ale faktycznie raz na znak przesunięcia w obrębiename
. Tak więc podejście to O (# names
*# phrases
*name length
) w złożoności w czasie wykonywania.Nowe podejście: kod
Ten kod jest również dostępny w pełnej wersji pastebin, ale skopiowałem go tutaj dla wygody. Pastebin ma również pełną definicję procedury, która zawiera zmienne
@minId
i@maxId
, które widzisz poniżej, aby zdefiniować granice bieżącej partii.Nowe podejście: plany zapytań
Najpierw generujemy podciąg, zaczynając od każdego przesunięcia znaku
Następnie utwórz indeks klastrowy na tych podciągach
Teraz dla każdego złego wyrażenia szukamy tych podciągów, aby zidentyfikować dowolne dopasowania. Następnie obliczamy liczbę różnych złych fraz, które pasują do jednego lub więcej podciągów tego ciągu. To naprawdę kluczowy krok; z powodu sposobu, w jaki zaindeksowaliśmy podciągi, nie musimy już sprawdzać pełnego iloczynu złych fraz i nazw. Ten krok, który dokonuje rzeczywistego obliczenia, stanowi jedynie około 10% rzeczywistego czasu wykonywania (reszta to wstępne przetwarzanie podciągów).
Na koniec wykonaj rzeczywistą instrukcję aktualizacji, używając a,
LEFT OUTER JOIN
aby przypisać liczbę 0 do dowolnych nazw, dla których nie znaleźliśmy złych fraz.Nowe podejście: analiza algorytmiczna
Nowe podejście można podzielić na dwie fazy, wstępne przetwarzanie i dopasowanie. Zdefiniujmy następujące zmienne:
N
= liczba nazwiskB
= liczba złych frazL
= średnia długość imienia, w znakachFaza wstępnego przetwarzania ma
O(N*L * LOG(N*L))
na celu utworzenieN*L
podciągów, a następnie ich posortowanie.Rzeczywiste dopasowanie ma
O(B * LOG(N*L))
na celu wyszukanie podłańcuchów dla każdej złej frazy.W ten sposób stworzyliśmy algorytm, który nie skaluje się liniowo z liczbą złych fraz, kluczowym odblokowaniem wydajności, gdy skalujemy do 3K fraz i więcej. Innymi słowy, oryginalna implementacja zajmuje około 10 razy tyle, ile przechodzimy od 300 złych fraz do 3 tys. Złych fraz. Podobnie zajęłoby to kolejne 10 razy więcej, gdybyśmy przeszli z 3 000 złych fraz do 30 000. Nowa implementacja będzie jednak skalować się sublinearnie i w rzeczywistości zajmuje mniej niż dwukrotność czasu mierzonego dla 3 000 niepoprawnych fraz przy skalowaniu do 30 000 niepoprawnych fraz.
Założenia / zastrzeżenia
SORT
na każde z tych podejść, ale jest to szczególnie ważne w przypadku nowego podejścia, aby podciągi były niezależne dla każdej partii i łatwo mieściły się w pamięci. W razie potrzeby możesz manipulować rozmiarem partii, ale nie byłoby rozsądnie wypróbować wszystkich wierszy 15 MM w jednej partii.Powiązany post na blogu
Aaron Bertrand bada bardziej szczegółowo tego rodzaju rozwiązanie w swoim blogu Jeden ze sposobów na uzyskanie indeksu dla wiodącej% wieloznacznej .
źródło
Odłóżmy na chwilę oczywistą kwestię poruszoną przez Aarona Bertranda w komentarzach na sekundę:
Fakt, że twoje podzapytanie używa symboli wieloznacznych po obu stronach, ma ogromny wpływ na możliwości sprzedaży . Aby zacytować ten post na blogu:
Zamień słowo „nakrętka” dla każdego „złego słowa” i „Produkt” dla
SourceTable
, a następnie połącz to z komentarzem Aarona i powinieneś zacząć rozumieć, dlaczego tak trudno (czytać niemożliwe) sprawić, by działał szybko przy użyciu twojego obecnego algorytmu.Widzę kilka opcji:
W zależności od wymagań poleciłbym opcję 3 lub 4.
źródło
po pierwsze to dziwna aktualizacja
Jak „%” + [Bad_Phrase]. [PHRASE] cię zabija,
który nie może korzystać z indeksu
Projektowanie danych nie jest optymalne pod względem szybkości
Czy potrafisz podzielić [Bad_Phrase]. [PHRASE] na pojedyncze frazy / słowa?
Jeśli ta sama fraza / słowo pojawia się więcej niż jedno, możesz wpisać je więcej niż jeden raz, jeśli chcesz, aby miał większą liczbę,
więc liczba wierszy w złej fazie wzrośnie.
Jeśli możesz, to będzie to znacznie szybsze
Nie jestem pewien, czy 2005 obsługuje to, ale Indeks pełnotekstowy i użycie Zawiera
źródło