Estoy tratando de producir un plan de consulta de muestra para mostrar por qué UNIONAR dos conjuntos de resultados puede ser mejor que usar OR en una cláusula JOIN. Un plan de consulta que he escrito me ha dejado perplejo. Estoy usando la base de datos StackOverflow con un índice no agrupado en Users.Reputation.
CREATE NONCLUSTERED INDEX IX_NC_REPUTATION ON dbo.USERS(Reputation)
SELECT DISTINCT Users.Id
FROM dbo.Users
INNER JOIN dbo.Posts
ON Users.Id = Posts.OwnerUserId
OR Users.Id = Posts.LastEditorUserId
WHERE Users.Reputation = 5
El plan de consulta se encuentra en https://www.brentozar.com/pastetheplan/?id=BkpZU1MZE , la duración de la consulta para mí es 4:37 min, 26612 filas devueltas.
No he visto este estilo de escaneo constante creado a partir de una tabla existente antes; no estoy familiarizado con por qué hay un escaneo constante ejecutado para cada fila, cuando un escaneo constante se usa generalmente para una sola fila ingresada por el usuario por ejemplo SELECT GETDATE (). ¿Por qué se usa aquí? Realmente agradecería alguna orientación al leer este plan de consulta.
Si divido ese OR en una UNIÓN, produce un plan estándar que se ejecuta en 12 segundos con las mismas 26612 filas devueltas.
SELECT Users.Id
FROM dbo.Users
INNER JOIN dbo.Posts
ON Users.Id = Posts.OwnerUserId
WHERE Users.Reputation = 5
UNION
SELECT Users.Id
FROM dbo.Users
INNER JOIN dbo.Posts
ON Users.Id = Posts.LastEditorUserId
WHERE Users.Reputation = 5
Interpreto este plan como hacer esto:
- Obtenga todas las 41782500 filas de las publicaciones (el número real de filas coincide con el escaneo de CI en las publicaciones)
- Por cada 41782500 filas en Publicaciones:
- Producir escalares:
- Expr1005: OwnerUserId
- Expr1006: OwnerUserId
- Expr1004: el valor estático 62
- Expr1008: LastEditorUserId
- Expr1009: LastEditorUserId
- Expr1007: el valor estático 62
- En la concatenada:
- Exp1010: Si Expr1005 (OwnerUserId) no es nulo, use ese otro uso Expr1008 (LastEditorUserID)
- Expr1011: si Expr1006 (OwnerUserId) no es nulo, úselo, de lo contrario use Expr1009 (LastEditorUserId)
- Expr1012: Si Expr1004 (62) es nulo, use eso, de lo contrario use Expr1007 (62)
- En el escalar Compute: no sé qué hace un ampersand.
- Expr1013: 4 [y?] 62 (Expr1012) = 4 y OwnerUserId IS NULL (NULL = Expr1010)
- Expr1014: 4 [y?] 62 (Expr1012)
- Expr1015: 16 y 62 (Expr1012)
- En Ordenar por ordenar por:
- Expr1013 Desc
- Expr1014 Asc
- Expr1010 Asc
- Expr1015 Desc
- En el intervalo de fusión, eliminó Expr1013 y Expr1015 (estas son entradas pero no salidas)
- En el índice de búsqueda debajo de los bucles anidados, use Expr1010 y Expr1011 como predicados de búsqueda, pero no entiendo cómo tiene acceso a estos cuando no ha hecho la unión de bucle anidado desde IX_NC_REPUTATION al subárbol que contiene Expr1010 y Expr1011 .
- La unión de bucles anidados devuelve solo los usuarios. ID que coinciden en el subárbol anterior. Debido al pushdown del predicado, se devuelven todas las filas devueltas de la búsqueda de índice en IX_NC_REPUTATION.
- Se unen los últimos Nested Loops: para cada registro de Publicaciones, genera Users.Id donde se encuentra una coincidencia en el conjunto de datos a continuación.
SELECT Users.Id FROM dbo.Users WHERE Users.Reputation = 5 AND EXISTS (SELECT 1 FROM dbo.Posts WHERE Users.Id IN (Posts.OwnerUserId, Posts.LastEditorUserId) ) ;
SELECT Users.Id FROM dbo.Users WHERE Users.Reputation = 5 AND ( EXISTS (SELECT 1 FROM dbo.Posts WHERE Users.Id = Posts.OwnerUserId) OR EXISTS (SELECT 1 FROM dbo.Posts WHERE Users.Id = Posts.LastEditorUserId) ) ;