Tengo una consulta como la siguiente:
DELETE FROM tblFEStatsBrowsers WHERE BrowserID NOT IN (
SELECT DISTINCT BrowserID FROM tblFEStatsPaperHits WITH (NOLOCK) WHERE BrowserID IS NOT NULL
)
tblFEStatsBrowsers tiene 553 filas.
tblFEStatsPaperHits tiene 47.974.301 filas.
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
)
Hay un índice agrupado en tblFEStatsPaperHits que no incluye BrowserID. Por lo tanto, realizar la consulta interna requerirá una exploración completa de la tabla de tblFEStatsPaperHits, lo cual está totalmente bien.
Actualmente, se ejecuta una exploración completa para cada fila en tblFEStatsBrowsers, lo que significa que tengo 553 exploraciones de tabla completa de tblFEStatsPaperHits.
Reescribir solo a WHERE EXISTS no cambia el plan:
DELETE FROM tblFEStatsBrowsers WHERE NOT EXISTS (
SELECT * FROM tblFEStatsPaperHits WITH (NOLOCK) WHERE BrowserID = tblFEStatsBrowsers.BrowserID
)
Sin embargo, como lo sugirió Adam Machanic, agregar una opción HASH JOIN resulta en un plan de ejecución óptimo (solo un escaneo de tblFEStatsPaperHits):
DELETE FROM tblFEStatsBrowsers WHERE NOT EXISTS (
SELECT * FROM tblFEStatsPaperHits WITH (NOLOCK) WHERE BrowserID = tblFEStatsBrowsers.BrowserID
) OPTION (HASH JOIN)
Ahora, esto no es tanto una cuestión de cómo solucionar esto: puedo usar la OPCIÓN (HASH JOIN) o crear una tabla temporal manualmente. Me pregunto por qué el optimizador de consultas usaría el plan que usa actualmente.
Dado que el QO no tiene ninguna estadística en la columna BrowserID, supongo que está asumiendo lo peor: 50 millones de valores distintos, lo que requiere una mesa de trabajo bastante grande en memoria / tempdb. Como tal, la forma más segura es realizar exploraciones para cada fila en tblFEStatsBrowsers. No existe una relación de clave externa entre las columnas BrowserID en las dos tablas, por lo que el QO no puede deducir ninguna información de tblFEStatsBrowsers.
¿Es esta, tan simple como parece, la razón?
Actualización 1
Para dar un par de estadísticas: OPCIÓN (HASH JOIN):
208.711 lecturas lógicas (12 escaneos)
OPCIÓN (LOOP JOIN, HASH GROUP):
11.008.698 lecturas lógicas (~ escaneo por ID de navegador (339))
Sin opciones:
11.008.775 lecturas lógicas (~ escaneo por ID de navegador (339))
Actualización 2
Excelentes respuestas, todos ustedes, ¡gracias! Difícil elegir solo uno. Aunque Martin fue el primero y Remus proporciona una solución excelente, tengo que dárselo al Kiwi para que se vuelva mental en los detalles :)