Dlaczego drugie INSERT
stwierdzenie jest ~ 5 razy wolniejsze niż pierwsze?
Biorąc pod uwagę ilość wygenerowanych danych dziennika, uważam, że drugi nie kwalifikuje się do minimalnego rejestrowania. Jednak dokumentacja w Przewodniku wydajności ładowania danych wskazuje, że oba wkładki powinny mieć możliwość minimalnego logowania. Jeśli więc minimalne rejestrowanie jest kluczową różnicą w wydajności, dlaczego drugie zapytanie nie kwalifikuje się do minimalnego rejestrowania? Co można zrobić, aby poprawić sytuację?
Zapytanie nr 1: Wstawianie wierszy 5 mm za pomocą polecenia WSTAW ... Z (TABLOCK)
Rozważ następujące zapytanie, które wstawia wiersze 5 mm do sterty. To zapytanie wykonuje 1 second
i generuje 64MB
dane dziennika transakcji zgłoszone przez sys.dm_tran_database_transactions
.
CREATE TABLE dbo.minimalLoggingTest (n INT NOT NULL)
GO
INSERT INTO dbo.minimalLoggingTest WITH (TABLOCK) (n)
SELECT n
-- Any table/view/sub-query that correctly estimates that it will generate 5MM rows
FROM dbo.fiveMillionNumbers
-- Provides greater consistency on my laptop, where other processes are running
OPTION (MAXDOP 1)
GO
Zapytanie nr 2: Wstawienie tych samych danych, ale SQL nie docenia liczby wierszy
Rozważmy teraz bardzo podobne zapytanie, które działa na dokładnie tych samych danych, ale zdarza się, że rysuje z tabeli (lub złożonej SELECT
instrukcji z wieloma złączeniami w moim rzeczywistym przypadku produkcyjnym), gdzie oszacowanie liczności jest zbyt niskie. To zapytanie wykonuje się 5.5 seconds
i generuje 461MB
dane dziennika transakcji.
CREATE TABLE dbo.minimalLoggingTest (n INT NOT NULL)
GO
INSERT INTO dbo.minimalLoggingTest WITH (TABLOCK) (n)
SELECT n
-- Any table/view/sub-query that produces 5MM rows but SQL estimates just 1000 rows
FROM dbo.fiveMillionNumbersBadEstimate
-- Provides greater consistency on my laptop, where other processes are running
OPTION (MAXDOP 1)
GO
Pełny skrypt
Zobacz tę Pastebin, aby uzyskać pełny zestaw skryptów do generowania danych testowych i wykonywania jednego z tych scenariuszy. Pamiętaj, że musisz użyć bazy danych, która jest w SIMPLE
modelu odzyskiwania .
Kontekst biznesowy
Częściowo poruszamy się wokół milionów wierszy danych i ważne jest, aby te operacje były jak najbardziej wydajne, zarówno pod względem czasu wykonania, jak i obciążenia dysku we / wy. Początkowo mieliśmy wrażenie, że utworzenie tabeli stosu i użycie INSERT...WITH (TABLOCK)
było dobrym sposobem na zrobienie tego, ale teraz jesteśmy mniej pewni, biorąc pod uwagę, że zaobserwowaliśmy sytuację pokazaną powyżej w rzeczywistym scenariuszu produkcyjnym (choć przy bardziej złożonych zapytaniach, a nie wersja uproszczona tutaj).
SELECT
instrukcja z licznymi złączeniami, które generują zestaw wyników dlaINSERT
.UPDATE STATISTICS
Połączenia te dają słabe oszacowania liczności dla operatora wstawiania ostatecznej tabeli (który symulowałem w skrypcie repro poprzez złe wywołanie), a zatem nie jest to tak proste jak wydanieUPDATE STATISTICS
polecenia naprawienia problemu. Zgadzam się całkowicie, że uproszczenie zapytania, aby łatwiej było zrozumieć Cardinality Estimator, może być dobrym podejściem, ale wdrożenie określonej złożonej logiki biznesowej nie jest trywialne.