Estou tentando produzir um plano de consulta de exemplo para mostrar por que UNIAR dois conjuntos de resultados pode ser melhor do que usar OR em uma cláusula JOIN. Um plano de consulta que escrevi me deixou perplexo. Estou usando o banco de dados StackOverflow com um índice não clusterizado em 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
O plano de consulta está em https://www.brentozar.com/pastetheplan/?id=BkpZU1MZE , a duração da consulta para mim é de 4:37 min, com retorno de 26612 linhas.
Eu nunca vi esse estilo de varredura constante sendo criado a partir de uma tabela existente antes - não estou familiarizado com o motivo pelo qual uma varredura constante é executada para cada linha, quando uma varredura constante geralmente é usada para uma única linha inserida pelo usuário por exemplo, SELECT GETDATE (). Por que é usado aqui? Eu realmente aprecio algumas orientações ao ler este plano de consulta.
Se eu dividir esse OR em um UNION, ele produzirá um plano padrão em execução em 12 segundos com as mesmas 26612 linhas retornadas.
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
Eu interpreto esse plano da seguinte maneira:
- Obter todas as 41782500 linhas de Postagens (o número real de linhas corresponde à verificação do IC nas Postagens)
- Para cada 41782500 linhas em Postagens:
- Produza escalares:
- Expr1005: OwnerUserId
- Expr1006: OwnerUserId
- Expr1004: O valor estático 62
- Expr1008: LastEditorUserId
- Expr1009: LastEditorUserId
- Expr1007: O valor estático 62
- No concatenar:
- Exp1010: Se Expr1005 (OwnerUserId) não for nulo, use o resto Expr1008 (LastEditorUserID)
- Expr1011: Se Expr1006 (OwnerUserId) não for nulo, use isso; caso contrário, use Expr1009 (LastEditorUserId)
- Expr1012: Se Expr1004 (62) for nulo, use-o; caso contrário, use Expr1007 (62)
- No escalar de computação: não sei o que um e comercial faz.
- Expr1013: 4 [e?] 62 (Expr1012) = 4 e OwnerUserId É NULL (NULL = Expr1010)
- Expr1014: 4 [e?] 62 (Expr1012)
- Expr1015: 16 e 62 (Expr1012)
- Na ordem Classificar por:
- Expr1013 Desc
- Expr1014 Asc
- Expr1010 Asc
- Expr1015 Desc
- No intervalo de mesclagem, ele removeu o Expr1013 e o Expr1015 (são entradas, mas não saídas)
- Na busca de índice abaixo da junção de loops aninhados, ele está usando Expr1010 e Expr1011 como predicados de busca, mas não entendo como ele tem acesso a eles quando não fez a junção de loop aninhado de IX_NC_REPUTATION à subárvore que contém Expr1010 e Expr1011 .
- A associação Loops aninhados retorna apenas os Users.IDs que têm uma correspondência na subárvore anterior. Devido ao empilhamento de predicado, todas as linhas retornadas da busca de índice em IX_NC_REPUTATION são retornadas.
- Os últimos Loops aninhados ingressam: Para cada registro de Postagens, produza Users.Id onde uma correspondência é encontrada no conjunto de dados abaixo.
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) ) ;