Ich habe eine Frage wie die folgende:
DELETE FROM tblFEStatsBrowsers WHERE BrowserID NOT IN (
SELECT DISTINCT BrowserID FROM tblFEStatsPaperHits WITH (NOLOCK) WHERE BrowserID IS NOT NULL
)
tblFEStatsBrowsers hat 553 Zeilen.
tblFEStatsPaperHits hat 47.974.301 Zeilen.
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
)
Es gibt einen Clustered-Index für tblFEStatsPaperHits, der keine BrowserID enthält. Das Ausführen der inneren Abfrage erfordert daher einen vollständigen Tabellenscan von tblFEStatsPaperHits - was völlig in Ordnung ist.
Derzeit wird in tblFEStatsBrowsers für jede Zeile ein vollständiger Scan ausgeführt. Das bedeutet, dass ich 553 vollständige Tabellenscans von tblFEStatsPaperHits habe.
Das Umschreiben auf einen WHERE EXISTS ändert den Plan nicht:
DELETE FROM tblFEStatsBrowsers WHERE NOT EXISTS (
SELECT * FROM tblFEStatsPaperHits WITH (NOLOCK) WHERE BrowserID = tblFEStatsBrowsers.BrowserID
)
Wie von Adam Machanic vorgeschlagen, führt das Hinzufügen einer HASH JOIN-Option jedoch zu einem optimalen Ausführungsplan (nur ein einziger Scan von tblFEStatsPaperHits):
DELETE FROM tblFEStatsBrowsers WHERE NOT EXISTS (
SELECT * FROM tblFEStatsPaperHits WITH (NOLOCK) WHERE BrowserID = tblFEStatsBrowsers.BrowserID
) OPTION (HASH JOIN)
Nun ist dies nicht so sehr eine Frage, wie man das behebt - ich kann entweder die OPTION (HASH JOIN) verwenden oder manuell eine temporäre Tabelle erstellen. Ich frage mich eher, warum das Abfrageoptimierungsprogramm jemals den Plan verwenden würde, den es derzeit ausführt.
Da die QO keine Statistiken in der BrowserID-Spalte hat, wird davon ausgegangen, dass es sich um die schlechtesten Werte handelt - 50 Millionen verschiedene Werte, weshalb eine ziemlich große In-Memory / Tempdb-Arbeitstabelle erforderlich ist. Daher ist es am sichersten, in tblFEStatsBrowsers für jede Zeile einen Scan durchzuführen. Es gibt keine Fremdschlüsselbeziehung zwischen den BrowserID-Spalten in den beiden Tabellen, sodass der QO keine Informationen von tblFEStatsBrowsers ableiten kann.
Ist das so einfach, wie es sich anhört, der Grund?
Update 1
Um ein paar Statistiken zu geben: OPTION (HASH JOIN):
208.711 logische Lesevorgänge (12 Scans)
OPTION (LOOP JOIN, HASH GROUP):
11.008.698 logische Lesevorgänge (~ Scan per BrowserID (339))
Keine Optionen:
11.008.775 logische Lesevorgänge (~ Scan per BrowserID (339))
Update 2
Hervorragende Antworten, ihr alle - danke! Schwierig, nur einen auszuwählen. Obwohl Martin der erste war und Remus eine ausgezeichnete Lösung bietet, muss ich es der Kiwi geben, um die Details im Kopf zu behalten :)