TL;DR;
Dada uma exibição indexada que tem JOIN
entre duas tabelas que já possuem um relacionamento de chave estrangeira junto com um WHERE
predicado na tabela pai.
Ao inserir na tabela pai da chave estrangeira, o compilador está adicionando manutenção de índice ao plano, mesmo que seja provável que não existam linhas correspondentes.
Isso é um bug ou talvez uma otimização perdida? Ou existe alguma falácia lógica ou algébrica em que estou?
Configurar
CREATE TABLE Parent (Id int identity primary key, SomeCol bit not null, OtherCol int not null);
CREATE TABLE Child (Id int identity primary key, ParentId int not null references Parent (Id) INDEX IX_Parent NONCLUSTERED);
CREATE VIEW dbo.vChild
WITH SCHEMABINDING
AS
SELECT c.Id, c.ParentId
FROM dbo.Child c
JOIN dbo.Parent p ON p.Id = c.ParentId
-- WHERE p.SomeCol = 0; -- problem dependent on this line
CREATE UNIQUE CLUSTERED INDEX CX_vChild ON vChild (Id)
db<>fiddle com WHERE
db<>fiddle sem WHERE
Neste estágio, qualquer UPDATE
coluna que afete na visão, e qualquer DELETE
, das tabelas da visão acionam, com razão, a manutenção da visão. O compilador pegará as linhas modificadas, as colocará em spool e as alimentará através das junções da visão, enviando quaisquer resultados para o índice da visão.
O mesmo pode ser dito para inserções para Child
, porque uma linha Parent
já pode existir para (que se qualifica em relação a WHERE
) e, portanto, a nova Child
linha pode se qualificar para a junção.
Problema
Ao inserir em Parent
, é provável que a manutenção do índice não precise ser feita. Uma linha correspondente ainda não pode existir Child
devido ao relacionamento de chave estrangeira, portanto, não pode haver linhas da inserção que se qualifiquem para a exibição.
Se você executar o script a seguir, verá que nenhuma manutenção de exibição é feita.
INSERT Parent (SomeCol, OtherCol)
VALUES (0, 100);
Muito claramente, o compilador pode deduzir que a manutenção da visão não é necessária aqui.
No entanto, se você descomentar a linha WHERE p.SomeCol = 0
na definição de exibição, de repente você obterá manutenção de exibição. Portanto, a adição de outra coluna à exibição, que não é uma coluna de junção e não possui um relacionamento de chave estrangeira, causa isso. Isso ocorre apesar da mesma lógica relacional aplicada: a inserção provavelmente ainda não se qualifica para a exibição, devido à coluna de chave estrangeira que ainda está lá.
Curiosamente, o compilador ainda pode reconhecer alguns casos em que a inserção não se qualificará para a exibição (e apesar desse exemplo específico ser parametrizado automaticamente).
Aqui o compilador reconhece que SomeCol
falha o WHERE
, e não há necessidade de fazer manutenção de índice.
INSERT Parent (SomeCol, OtherCol)
VALUES (1, 100);
O otimizador não faz o tipo de raciocínio que você descreve.
Em vez disso, depende de um conjunto padrão de recursos frequentemente úteis, fáceis de implementar e rápidos de verificar, como detecção de contradição e remoção de junção redundante para produzir simplificações úteis.
É a interação entre esses recursos modulares que pode produzir comportamentos aparentemente complexos que as pessoas às vezes confundem com otimização extensiva e análise semântica profunda.
O comportamento observado pode ser explicado por referência a esses recursos padrão do otimizador e à maneira como as visualizações indexadas são mantidas usando a álgebra delta .
Essa consulta toca apenas a tabela Filho no plano de execução porque a linha pai tem a garantia de existir:
Quando você adiciona um predicado na tabela Pai, isso não é mais logicamente possível:
O mesmo mecanismo subjacente é responsável por remover a junção na subárvore de manutenção produzida através da álgebra delta. Você pode ver um pouco disso com o sinalizador de rastreamento 8619, quando a saída de depuração inclui:
Para uma inserção, o delta produzido é muito semelhante às consultas ilustrativas acima. Para uma atualização, exclusão ou mesclagem, os deltas podem ser diferentes porque informações diferentes podem ser necessárias para manter a exibição. (No seu exemplo específico, é difícil ver o que pode ser atualizado na tabela Pai, mas a questão é principalmente sobre uma inserção.)
Quando a
WHERE
cláusula está presente, um Filtro nesse predicado aparece na parte de manutenção do plano.Para esta instrução, é detectada uma contradição entre o filtro em SomeCol = 0 e o valor de SomeCol especificado na inserção. Essa contradição resulta em toda a subárvore de manutenção sendo garantida vazia e, portanto, removida:
Para esta afirmação, não há contradição, mas o Filtro é redundante e, portanto, removido. A subárvore de manutenção não é garantida vazia e, portanto, não é removida:
Se você usar, por exemplo, variáveis ou parâmetros locais em vez de literais, o Filtro reaparecerá.
O exemplo dado não é é simples parametrizado. Ele se qualificou para consideração, mas a parametrização não foi determinada como segura. Não se deixe enganar pela presença de marcadores como
@1
no texto. Consulte Por que um plano com otimização FULL mostra parametrização simples?