Estou ajustando uma consulta que está lenta, reduzi a raiz do problema para estar bem no início do plano de execução, onde o SQL Server faz uma estimativa incorreta em um filtro WHERE IS NULL que suporta uma anti-junção à esquerda - SQL O servidor estima 1 linha e favorece algumas varreduras de índice através de um loop aninhado, pensando que só as executará uma vez, quando na verdade isso acontece milhares de vezes:
Consegui criar um MCVE para replicar o problema.
Configurar o ambiente de teste
/* INSERT 35000 dinstinct random numbers into a table */
CREATE TABLE #TableA
(
ID BIGINT NULL
)
INSERT INTO #TableA
SELECT DISTINCT
TOP 35000
a.Random
FROM (
SELECT TOP 50000
ABS(CHECKSUM(NewId())) % 20000000 AS Random
FROM sys.messages
) a
GO
/* add a further 15000 that already exist in the table. Use a loop to increase the possibility of duplicates */
INSERT INTO #TableA
SELECT TOP 1000
ID
FROM #TableA a
ORDER BY NEWID()
GO 15
/* Insert 10000 numbers into another table, that are in the first table */
CREATE TABLE #TableB
(
ID BIGINT NOT NULL
)
INSERT INTO #TableB
SELECT TOP 10000
*
FROM #TableA
/* insert 80000 distinct random numbers that are not in the first table */
INSERT INTO #TableB
SELECT DISTINCT
TOP 80000
a.Random
FROM (
SELECT TOP 100000
ABS(CHECKSUM(NewId())) % 2000000 AS Random
FROM sys.messages
) a
LEFT JOIN #TableA b
ON a.Random = b.ID
WHERE b.ID IS NULL
Então, a consulta que sofre o problema é
SELECT a.ID
FROM #TableA a
LEFT JOIN #TableB b
ON a.ID = b.ID
WHERE b.ID IS NULL
O que é bastante simples "mostre-me todos os IDs na Tabela A que não estão na Tabela B"
O plano de execução do meu ambiente de teste está aqui
Podemos ver algo muito semelhante acontecendo como vemos no plano acima, em termos do operador de filtro - o SQL Server une as duas tabelas e depois filtra os registros que estão na tabela da esquerda, mas não na tabela da direita e isso subestima enormemente o número de linhas que correspondem a esse predicado
Se eu forçar a estimativa herdada, obterei uma estimativa muito melhor do operador
Acredito que uma das principais diferenças entre o estimador antigo e os novos estimadores é como eles diferem na suposição da correlação entre dois predicados - o antigo assume que há pouca correlação entre dois predicados, enquanto o novo estimador é mais otimista e assume uma maior correlação?
Minhas perguntas são
- O que causa essa subestimação no estimador de cardinalidade mais recente?
- Existe uma maneira de consertar isso além de forçar o modelo de compatibilidade mais antigo?
O CE padrão usa alinhamento aproximado nos valores mínimo/máximo do histograma.
O CE legado alinha histogramas com diferentes valores de passo individualmente usando interpolação linear.
A computação legada geralmente produz resultados mais precisos, mas foi considerada menos consistente.
O novo comportamento é intencional. Ele pode ser desativado usando o sinalizador de rastreamento 9474 não documentado e sem suporte. Isso não é uma recomendação. Você deve encontrar uma maneira melhor de contornar a estimativa problemática, como usar uma tabela temporária, etc.