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 = Posts.OwnerUserId) OR EXISTS (SELECT 1 FROM dbo.Posts WHERE Users.Id = Posts.LastEditorUserId) ) ;
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) ) ;
Respostas:
O plano é semelhante ao que eu faço em mais detalhes aqui .
A
Posts
tabela é digitalizada.Para cada linha extrai o
OwnerUserId
eLastEditorUserId
. Isso é semelhante ao modo comoUNPIVOT
funciona. Você vê um único operador de varredura constante no plano abaixo, criando as duas linhas de saída para cada linha de entrada.Nesse caso, o plano é um pouco mais complexo, pois a semântica
or
é que, se os dois valores de coluna forem os mesmos, apenas uma linha deverá ser emitida a partir da junçãoUsers
(não duas)Eles são então colocados no intervalo de mesclagem para que, no caso de os valores serem os mesmos, o intervalo seja reduzido e apenas uma busca seja executada
Users
- caso contrário, duas buscas serão executadas contra ele.O valor
62
é um sinalizador, o que significa que a busca deve ser uma busca de igualdade.A respeito de
Eles são definidos no operador de concatenação destacado em amarelo. Está no lado externo dos loops aninhados destacados em amarelo. Portanto, isso é executado antes da busca destacada em amarelo no interior dos loops aninhados.
Uma reescrita que fornece um plano semelhante (embora com o intervalo de mesclagem substituído por uma união de mesclagem) está abaixo, caso isso ajude.
Dependendo de quais índices estão disponíveis na
Posts
tabela, uma variante dessa consulta pode ser mais eficiente que aUNION ALL
solução proposta . (a cópia do banco de dados que possuo não possui um índice útil para isso e a solução proposta faz duas varreduras completasPosts
. A seguir, em uma varredura)fonte