У меня есть запрос, подобный следующему:
DELETE FROM tblFEStatsBrowsers WHERE BrowserID NOT IN (
SELECT DISTINCT BrowserID FROM tblFEStatsPaperHits WITH (NOLOCK) WHERE BrowserID IS NOT NULL
)
tblFEStatsBrowsers получил 553 строки.
У tblFEStatsPaperHits есть 47,974,301 строк.
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
)
В tblFEStatsPaperHits есть кластерный индекс, который не включает BrowserID. Выполнение внутреннего запроса, таким образом, потребует полного сканирования таблицы tblFEStatsPaperHits - что вполне нормально.
В настоящее время полное сканирование выполняется для каждой строки в tblFEStatsBrowsers, что означает, что у меня есть 553 полных сканирования таблицы tblFEStatsPaperHits.
Переписывание только ГДЕ СУЩЕСТВУЕТ не меняет план:
DELETE FROM tblFEStatsBrowsers WHERE NOT EXISTS (
SELECT * FROM tblFEStatsPaperHits WITH (NOLOCK) WHERE BrowserID = tblFEStatsBrowsers.BrowserID
)
Однако, как предлагает Адам Мачаник, добавление опции HASH JOIN приводит к оптимальному плану выполнения (всего одно сканирование tblFEStatsPaperHits):
DELETE FROM tblFEStatsBrowsers WHERE NOT EXISTS (
SELECT * FROM tblFEStatsPaperHits WITH (NOLOCK) WHERE BrowserID = tblFEStatsBrowsers.BrowserID
) OPTION (HASH JOIN)
Теперь вопрос не в том, как это исправить - я могу либо использовать OPTION (HASH JOIN), либо создать временную таблицу вручную. Мне больше интересно, почему оптимизатор запросов будет использовать план, который он использует в настоящее время.
Поскольку в QO нет статистики по столбцу BrowserID, я предполагаю, что оно предполагает худшее - 50 миллионов различных значений, что требует довольно большой рабочей таблицы в памяти / tempdb. Таким образом, самый безопасный способ - выполнить сканирование для каждой строки в tblFEStatsBrowsers. Между столбцами BrowserID в этих двух таблицах нет связи по внешнему ключу, поэтому QO не может вычесть какую-либо информацию из tblFEStatsBrowsers.
Это так просто, как кажется, причина?
Обновление 1
Чтобы дать пару характеристик: ОПЦИЯ (HASH JOIN):
208,711 логических чтений (12 сканирований)
ОПЦИЯ (LOOP JOIN, HASH GROUP):
11,008,698 логических операций чтения (~ сканирование на BrowserID (339))
Нет параметров:
11,008,775 логических операций чтения (~ сканирование на BrowserID (339))
Обновление 2
Отличные ответы, всем вам - спасибо! Трудно выбрать только один. Хотя Мартин был первым, а Ремус предлагает отличное решение, я должен дать его киви за то, что он разбирается в деталях :)