Po dodaniu dwóch kolumn do mojego zapytania zapytanie nie odpowiada. Typ kolumny to nvarchar(2000)
. To trochę niezwykłe.
- Wersja SQL Server to 2014.
- Istnieje tylko jeden indeks główny.
- Całe rekordy to tylko 1000 wierszy.
Oto plan wykonania przed ( XML showplan ):
Plan wykonania po ( XML showplan ):
Oto zapytanie:
select top(100)
Batch_Tasks_Queue.id,
btq.id,
Batch_Tasks_Queue.[Parameters], -- this field
btq.[Parameters] -- and this field
from
Batch_Tasks_Queue with(nolock)
inner join Batch_Tasks_Queue btq with(nolock) on Batch_Tasks_Queue.Start_Time < btq.Start_Time
and btq.Start_Time < Batch_Tasks_Queue.Finish_Time
and Batch_Tasks_Queue.id <> btq.id
and btq.Start_Time is not null
and btq.State in (3, 4)
where
Batch_Tasks_Queue.Start_Time is not null
and Batch_Tasks_Queue.State in (3, 4)
and Batch_Tasks_Queue.Operation_Type = btq.Operation_Type
and Batch_Tasks_Queue.Operation_Type not in (23, 24, 25, 26, 27, 28, 30)
order by
Batch_Tasks_Queue.Start_Time desc
Cały wynik to 17 wierszy. Brudne dane (wskazówka nolock) nie są ważne.
Oto struktura tabeli:
CREATE TABLE [dbo].[Batch_Tasks_Queue](
[Id] [int] NOT NULL,
[OBJ_VERSION] [numeric](8, 0) NOT NULL,
[Operation_Type] [numeric](2, 0) NULL,
[Request_Time] [datetime] NOT NULL,
[Description] [varchar](1000) NULL,
[State] [numeric](1, 0) NOT NULL,
[Start_Time] [datetime] NULL,
[Finish_Time] [datetime] NULL,
[Parameters] [nvarchar](2000) NULL,
[Response] [nvarchar](max) NULL,
[Billing_UserId] [int] NOT NULL,
[Planned_Start_Time] [datetime] NULL,
[Input_FileId] [uniqueidentifier] NULL,
[Output_FileId] [uniqueidentifier] NULL,
[PRIORITY] [numeric](2, 0) NULL,
[EXECUTE_SEQ] [numeric](2, 0) NULL,
[View_Access] [numeric](1, 0) NULL,
[Seeing] [numeric](1, 0) NULL,
CONSTRAINT [PKBachTskQ] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [Batch_Tasks_QueueData]
) ON [Batch_Tasks_QueueData] TEXTIMAGE_ON [Batch_Tasks_QueueData]
GO
SET ANSI_PADDING OFF
GO
ALTER TABLE [dbo].[Batch_Tasks_Queue] WITH NOCHECK ADD CONSTRAINT [FK0_BtchTskQ_BlngUsr] FOREIGN KEY([Billing_UserId])
REFERENCES [dbo].[BILLING_USER] ([ID])
GO
ALTER TABLE [dbo].[Batch_Tasks_Queue] CHECK CONSTRAINT [FK0_BtchTskQ_BlngUsr]
GO
sql-server
query-performance
sql-server-2014
Hamid Fathi
źródło
źródło
Odpowiedzi:
Podsumowanie
Główne problemy to:
Detale
Te dwa plany są zasadniczo bardzo podobne, chociaż wydajność może się bardzo różnić:
Planuj z dodatkowymi kolumnami
Biorąc tę z dodatkowymi kolumnami, która nie kończy się w rozsądnym czasie:
Ciekawe funkcje to:
Start_Time
wartość nie jest pusta,State
wynosi 3 lub 4 iOperation_Type
jest jedną z wymienionych wartości. Tabela jest w pełni skanowana raz, a każdy wiersz jest testowany pod kątem wspomnianych predykatów. Tylko wiersze, które przejdą wszystkie testy, przechodzą do sortowania. Optymalizator szacuje, że zakwalifikuje się 38 283 wierszy.Start_Time DESC
. Jest to ostateczna kolejność prezentacji żądana przez zapytanie.Start_Time
jest różne od zera, aState
jest 3 lub 4. To oszacowanych produkować 400,875 rzędów na każdej iteracji. Ponad 94.2791 iteracji, całkowita liczba wierszy wynosi prawie 38 milionów.Operation_Type
pasuje, czyStart_Time
z węzła 4 jest mniejszy niżStart_Time
z węzła 5, czyStart_Time
z węzła 5 jest mniejszy niżFinish_Time
z węzła 4 i czy te dwieId
wartości się nie zgadzają.Wielka nieefektywność występuje oczywiście w krokach 6 i 7 powyżej. Pełne skanowanie tabeli w węźle 5 dla każdej iteracji jest nawet nieco rozsądne, jeśli dzieje się to tylko 94 razy, jak przewiduje optymalizator. Zestaw około 38 milionów wierszy porównań w węźle 2 to także duży koszt.
Co istotne, oszacowanie celu rzędu rzędu 93/94 również jest prawdopodobnie błędne, ponieważ zależy od rozkładu wartości. Optymalizator zakłada równomierny rozkład przy braku bardziej szczegółowych informacji. Mówiąc prościej, oznacza to, że jeśli oczekuje się, że kwalifikuje się 1% wierszy w tabeli, optymalizator powoduje, że aby znaleźć 1 pasujący wiersz, musi odczytać 100 wierszy.
Jeśli uruchomisz tę kwerendę do końca (co może zająć bardzo dużo czasu), najprawdopodobniej odkryjesz, że z Sortowania trzeba odczytać wiele więcej niż 93/94 wierszy, aby ostatecznie wygenerować 100 wierszy. W najgorszym przypadku setny rząd zostałby znaleziony przy użyciu ostatniego rzędu z sortowania. Zakładając, że oszacowanie optymalizatora w węźle 4 jest prawidłowe, oznacza to uruchomienie skanowania w węźle 5 38 284 razy, w sumie około 15 miliardów wierszy. Może być więcej, jeśli szacunki skanowania są również wyłączone.
Ten plan wykonania zawiera również ostrzeżenie o braku indeksu:
Optymalizator ostrzega o tym, że dodanie indeksu do tabeli poprawiłoby wydajność.
Planuj bez dodatkowych kolumn
Zasadniczo jest to dokładnie ten sam plan, co poprzedni, z dodaniem szpuli indeksu w węźle 6 i filtra w węźle 5. Ważne różnice to:
Operation_Type
iStart_Time
, zId
kolumną bez klucza.Operation_Type
,Start_Time
,Finish_Time
iId
ze skanowania w węźle 4 są przemieszczane do gałęzi wewnętrzna po stronie zewnętrznej, jak odniesienia.Operation_Type
odpowiada bieżącej zewnętrznej wartości odniesienia, iStart_Time
znajduje się w zakresie określonym przez odniesienia zewnętrzneStart_Time
iFinish_Time
.Id
wartości z bufora indeksu pod kątem nierówności względem bieżącej zewnętrznej wartości odniesieniaId
.Najważniejsze ulepszenia to:
Operation_Type
,Start_Time
) zId
dołączoną kolumną pozwala na łączenie zagnieżdżonych pętli indeksu. Indeks służy do wyszukiwania pasujących wierszy na każdej iteracji zamiast skanowania całego stołu za każdym razem.Tak jak poprzednio, optymalizator zawiera ostrzeżenie o brakującym indeksie:
Wniosek
Plan bez dodatkowych kolumn jest szybszy, ponieważ optymalizator postanowił utworzyć dla ciebie tymczasowy indeks.
Plan z dodatkowymi kolumnami spowodowałby, że tymczasowy indeks byłby droższy w budowie.
[Parameters
Kolumny] tonvarchar(2000)
, które sumują się z 4000 bajtów do każdej linii wskaźnika. Dodatkowy koszt jest wystarczający, aby przekonać optymalizator, że utworzenie tymczasowego indeksu przy każdym wykonaniu nie zwróci się.Optymalizator ostrzega w obu przypadkach, że lepszym rozwiązaniem byłby indeks stały. Idealna kompozycja indeksu zależy od szerszego obciążenia pracą. W przypadku tego konkretnego zapytania sugerowane indeksy są rozsądnym punktem wyjścia, ale należy zrozumieć korzyści i koszty z tym związane.
Rekomendacje
Dla tego zapytania korzystny byłby szeroki zakres możliwych indeksów. Ważne jest to, że potrzebny jest jakiś indeks nieklastrowany. Na podstawie dostarczonych informacji rozsądnym moim zdaniem byłby:
Kusiłbym również, by trochę lepiej zorganizować zapytanie i opóźnić wyszukiwanie szerokich
[Parameters]
kolumn w indeksie klastrowym, aż do znalezienia 100 pierwszych wierszy (używającId
jako klucza):Tam, gdzie
[Parameters]
kolumny nie są potrzebne, zapytanie można uprościć:FORCESEEK
Wskazówką jest to, aby pomóc zapewnić optymalizator wybiera indeksowanego zagnieżdżone pętle zaplanować (istnieje pokusa, oparte na kosztach dla optymalizatora, aby wybrać skrót lub (many-many) scalające inaczej, co nie ma tendencję do pracy również z tego typu zapytanie w praktyce. Oba kończą się dużymi resztkami; wiele elementów na wiadro w przypadku skrótu i wiele przewinięć do scalenia).Alternatywny
Gdyby zapytanie (w tym jego określone wartości) miało szczególne znaczenie dla wydajności odczytu, zamiast tego wziąłbym pod uwagę dwa przefiltrowane indeksy:
W przypadku zapytania, które nie wymaga
[Parameters]
kolumny, plan szacunkowy z wykorzystaniem przefiltrowanych indeksów wynosi:Skanowanie indeksu automatycznie zwraca wszystkie kwalifikujące się wiersze bez oceny jakichkolwiek dodatkowych predykatów. Dla każdej iteracji łączenia zagnieżdżonych pętli indeksu przeszukiwanie indeksu wykonuje dwie operacje wyszukiwania:
Operation_Type
iState
= 3, a następnie szuka zakresuStart_Time
wartości, a predykat resztkowy oId
nierówności.Operation_Type
iState
= 4, a następnie szuka zakresuStart_Time
wartości, a predykat resztkowy naId
nierówności.Tam, gdzie
[Parameters]
kolumna jest potrzebna, plan zapytań po prostu dodaje maksymalnie 100 wyszukiwań singletonów dla każdej tabeli:Na koniec należy rozważyć użycie wbudowanych standardowych typów liczb całkowitych zamiast, gdy ma
numeric
to zastosowanie.źródło
Utwórz następujący indeks:
źródło