Por que o MS SQL Server se recusa a usar o índice filtrado de suporte neste cenário?
-- demo data
CREATE TABLE #Test (
ID INT IDENTITY(1,1) NOT NULL CONSTRAINT PK_Test_ID PRIMARY KEY
,Col1 NVARCHAR(36) NOT NULL DEFAULT NEWID()
,Col2 NVARCHAR(20) NOT NULL DEFAULT N'' -- !!
);
WITH
L0 AS(SELECT 1 AS C UNION ALL SELECT 1 AS O), -- 2 rows
L1 AS(SELECT 1 AS C FROM L0 AS A CROSS JOIN L0 AS B), -- 4 rows
L2 AS(SELECT 1 AS C FROM L1 AS A CROSS JOIN L1 AS B), -- 16 rows
L3 AS(SELECT 1 AS C FROM L2 AS A CROSS JOIN L2 AS B), -- 256 rows
L4 AS(SELECT 1 AS C FROM L3 AS A CROSS JOIN L3 AS B), -- 65,536 rows
L5 AS(SELECT 1 AS C FROM L4 AS A CROSS JOIN L4 AS B), -- 4,294,967,296 rows
Nums AS(SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS N FROM L5)
INSERT INTO #Test(Col2)
SELECT TOP 100000 N''
FROM Nums;
INSERT INTO #Test(Col2)
VALUES(N'ABC');
-- FILTERED index to support filter predicate of a query
CREATE NONCLUSTERED INDEX IX_Test_Col2_filtered ON #Test (Col2 ASC) WHERE Col2 <> N'';
-- just checking statistics
DBCC SHOW_STATISTICS('#Test', 'IX_Test_Col2_filtered')
-- condition on variable = index scan :-(
DECLARE @Filter NVARCHAR(20) = N'ABC'
SELECT Col1
FROM #Test
WHERE Col2 = @Filter
AND Col2 <> N'';
Tudo corre como esperado ao usar literais.
-- condition on literal value - index seek + key lookup :-)
SELECT Col1
FROM #Test
WHERE Col2 = N'ABC';
Índices filtrados não podem usar variáveis/parâmetros, a menos que você esteja construindo sua consulta com SQL dinâmico para que a consulta acabe sendo executada com um literal.
Um bom artigo sobre o tema é Índices Filtrados e SQL Dinâmico de Jeremiah Peschka.
OU
Como Martin sugeriu, você pode adicionar
WITH (RECOMPILE)
à consulta, mas isso não deve ser usado sem entender as possíveis repercussões (consulte RECOMPILE Hints and Execution Plan Caching ).Quando você usa o valor constante no filtro,
Optimizer
torne o plano específico para esse valor. Desde queOptimizer
saiba oparameter
valor, ele usa oStatistics Histogram
para estimar o número de registros que podem ser retornados por uma consulta.Quando a variável local for usada na condição where,
Optimizer
faça um plano para "OPTIMIZE FOR UNKNOWN
". Ele não usaStatistics Histogram
, ele usa as informações noDensity Vector
.Portanto, o número de linhas a serem lidas será
100001
.Como sabemos na otimização da base de custos,
Optimizer
escolheremos o plano econômico rapidamente.Portanto, a escolha do índice não será viável para
Optimizer
.Se você
Index Hint
, então, ele usará,Index Seek
mas o custo será maior.O otimizador faz um plano
OPTIMIZE FOR UNKNOWN
porque, na próxima vez que você alterar o valorlocal variable
, ele reutilizará o mesmo plano.No mesmo caso, é benéfico e, em alguns casos, prejudicará o desempenho . A
OPTIMIZE FOR UNKNOWN
suposição é baseada em cálculoAll Density * Number of rows
.Agora eu crio Proc ,
Verifique o plano, o plano é exatamente semelhante ao
(dynamic)sp_executesql
.Vemos
Index seek
ekey look up
.sp_executesql
é um Proc. Variável@Filter1
se comporta exatamente como parâmetro de Proc.sp_executesql
não está usando@Filter
.Portanto
optimizer
, faça um plano para esse parâmetro. Quando você passar algum outro valor, ele reutilizará o mesmo plano. Isso é chamado deParameter sniffing
.Então basicamente o que eu disse está correto.
Um padrão alternativo, coloque a variável para pesquisar em uma tabela temporária e junte-se, tente o seguinte com planos de execução e estatísticas io on