Mam zapytanie podobne do następującego:
DELETE FROM tblFEStatsBrowsers WHERE BrowserID NOT IN (
SELECT DISTINCT BrowserID FROM tblFEStatsPaperHits WITH (NOLOCK) WHERE BrowserID IS NOT NULL
)
tblFEStatsBrowsers ma 553 wierszy.
tblFEStatsPaperHits ma 47.974.301 wierszy.
tblFEStatsBrowsers:
CREATE TABLE [dbo].[tblFEStatsBrowsers](
[BrowserID] [smallint] IDENTITY(1,1) NOT NULL,
[Browser] [varchar](50) NOT NULL,
[Name] [varchar](40) NOT NULL,
[Version] [varchar](10) NOT NULL,
CONSTRAINT [PK_tblFEStatsBrowsers] PRIMARY KEY CLUSTERED ([BrowserID] ASC)
)
tblFEStatsPaperHits:
CREATE TABLE [dbo].[tblFEStatsPaperHits](
[PaperID] [int] NOT NULL,
[Created] [smalldatetime] NOT NULL,
[IP] [binary](4) NULL,
[PlatformID] [tinyint] NULL,
[BrowserID] [smallint] NULL,
[ReferrerID] [int] NULL,
[UserLanguage] [char](2) NULL
)
Indeks tblFEStatsPaperHits zawiera klastrowany indeks, który nie zawiera BrowserID. Wykonanie wewnętrznego zapytania będzie zatem wymagało pełnego skanowania tabeli tblFEStatsPaperHits - co jest całkowicie OK.
Obecnie dla każdego wiersza w tblFEStatsBrowsers wykonywane jest pełne skanowanie, co oznacza, że mam 553 skanów pełnej tabeli dla tblFEStatsPaperHits.
Przepisanie tylko do GDZIE ISTNIEJE nie zmienia planu:
DELETE FROM tblFEStatsBrowsers WHERE NOT EXISTS (
SELECT * FROM tblFEStatsPaperHits WITH (NOLOCK) WHERE BrowserID = tblFEStatsBrowsers.BrowserID
)
Jednak, jak sugeruje Adam Machanic, dodanie opcji HASH JOIN skutkuje optymalnym planem wykonania (tylko jeden skan tblFEStatsPaperHits):
DELETE FROM tblFEStatsBrowsers WHERE NOT EXISTS (
SELECT * FROM tblFEStatsPaperHits WITH (NOLOCK) WHERE BrowserID = tblFEStatsBrowsers.BrowserID
) OPTION (HASH JOIN)
Teraz nie jest to tak bardzo pytanie, jak to naprawić - mogę albo użyć OPCJI (HASH JOIN), albo ręcznie utworzyć tabelę tymczasową. Zastanawiam się bardziej, dlaczego optymalizator zapytań kiedykolwiek używałby planu, który obecnie wykonuje.
Ponieważ QO nie ma żadnych statystyk w kolumnie BrowserID, domyślam się, że przyjmuje on najgorsze - 50 milionów różnych wartości, co wymaga dość dużego stołu roboczego w pamięci / tempdb. W związku z tym najbezpieczniejszym sposobem jest skanowanie dla każdego wiersza w tblFEStatsBrowsers. Nie ma relacji klucza obcego między kolumnami BrowserID w dwóch tabelach, więc QO nie może odjąć żadnych informacji od tblFEStatsBrowsers.
Czy to jest tak prosty, jak się wydaje, powód?
Aktualizacja 1
Aby podać kilka statystyk: OPCJA (HASH JOIN):
208,711 logicznych odczytów (12 skanów)
OPCJA (DOŁĄCZ DO PĘTLI, GRUPA HASH):
11.008.698 odczytów logicznych (~ skanowanie na identyfikator przeglądarki (339))
Brak opcji:
11.008.775 odczytów logicznych (~ skanowanie na identyfikator przeglądarki (339))
Aktualizacja 2
Doskonałe odpowiedzi dla was wszystkich - dzięki! Trudno wybrać tylko jeden. Chociaż Martin był pierwszy, a Remus zapewnia doskonałe rozwiązanie, muszę przekazać go Kiwi, aby zajął się szczegółami :)