Eu tenho uma tabela muito pequena com 12 linhas que podem ser criadas com a seguinte instrução:
CREATE TABLE dbo.SmallTable(ScoreMonth tinyint NOT NULL PRIMARY KEY,
ScoreGoal float NOT NULL
);
Eu tenho outra tabela com ≈100M de linhas que pode ser criada com as seguintes declarações:
CREATE TABLE dbo.SlowCrossApply(RecordKey nvarchar(12) NOT NULL,
Score1 decimal(3, 2) NOT NULL,
Score2 decimal(3, 2) NOT NULL,
Score3 decimal(3, 2) NOT NULL,
Score4 decimal(3, 2) NOT NULL,
Score5 decimal(3, 2) NOT NULL,
Score6 decimal(3, 2) NOT NULL,
FromToday bit NOT NULL
);
ALTER TABLE dbo.SlowCrossApply ADD CONSTRAINT i01PK PRIMARY KEY CLUSTERED(RecordKey ASC)
WITH(FILLFACTOR = 90, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON,
DATA_COMPRESSION = PAGE
);
CREATE NONCLUSTERED INDEX i02TodayRecords ON dbo.SlowCrossApply(FromToday)
INCLUDE (Score1, Score2, Score3, Score4, Score5, Score6)
WHERE FromToday = 1
WITH(FILLFACTOR = 100, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON,
DATA_COMPRESSION = PAGE
);
i02TodayRecords
tem ≈1M linhas nele. Quando executo a seguinte consulta - tive dificuldade em formatá-la para parecer limpo e evitar uma barra de rolagem horizontal - leva mais de 5 minutos para terminar:
SELECT b.RecordKey,
COALESCE(NULLIF(ROUND(((0.95 * (ROW_NUMBER() OVER(PARTITION BY a.Prefix
ORDER BY b.Score6 ASC
) - 1
)
)
/ COALESCE(NULLIF(COUNT(*) OVER(PARTITION BY a.Prefix) - 1, 0
), 1
)
) + 0.005, 2
), 0.96
), 0.95
) AS NewScore
FROM (SELECT LEFT(s.RecordKey, 2) AS Prefix,
CAST(ROUND(sm.ScoreGoal * COUNT(*), 0) AS int) AS Quant
FROM dbo.SlowCrossApply AS s
CROSS JOIN dbo.SmallTable AS sm
WHERE s.FromToday = 1 AND sm.ScoreMonth = MONTH(GETDATE())
GROUP BY LEFT(s.RecordKey, 2), sm.ScoreGoal
) AS a
CROSS APPLY (SELECT TOP(a.Quant) s2.RecordKey, s2.Score6
FROM dbo.SlowCrossApply AS s2
WHERE s2.FromToday = 1 AND s2.Score6 > 0 AND LEFT(s2.RecordKey, 2) = a.Prefix
ORDER BY s2.Score6 DESC
) AS b;
A subconsulta externa retorna apenas 10 linhas; e se eu fornecer uma dica para usar i02TodayRecords
ou colocar os resultados da subconsulta externa em uma variável de tabela, leva menos de 1 segundo. O resultado final retorna pouco mais de 8.000 linhas.
O plano de execução mostra que 64% do custo é devido a um spool de índice ansioso no índice clusterizado na Cross Apply
porção.
Eu sei que a dica de índice funciona (pelo menos por enquanto), mas espero evitar usar uma. Idealmente, eu também não seguiria a rota da variável de tabela. Existe algo que eu possa fazer para que o otimizador de consulta "saiba" utilizar i02TodayRecords
? Percebo que há muito mais informações que provavelmente são importantes e farei o possível para fornecer essas informações, se solicitadas.
Algumas informações potencialmente úteis: os índices têm menos de 1% de fragmentação. As estatísticas de ambos os índices foram atualizadas por meio de um FULLSCAN
, e o banco de dados está configurado para ter parametrização simples e sniffing de parâmetros — infelizmente, não posso alterar essas configurações. Em relação a este último, o otimizador de consulta não substituiu nenhum valor por parâmetros, ao contrário de outras consultas simples que executei, nas quais fui forçado a usar uma dica para utilizar um índice filtrado específico.
O problema que você provavelmente está enfrentando é em torno de SARGability , ou seja, usando a
LEFT
função em suaWHERE
cláusula:LEFT(s2.RecordKey, 2) = a.Prefix
Com isso, você fica preso executando a função para cada linha e comparando-a. Você não pode indexar para isso, como está. Colocar a transformação em uma CTE, exibição ou tabela derivada não ajudaria, nem escrever uma função para realizar a manipulação.
Uma maneira de contornar isso é criar e indexar uma coluna computada:
Que pode ser indexado. Também estou mudando um pouco sua definição de índice:
Outra alternativa seria despejar os resultados do seu
CROSS JOIN
em uma tabela temporária:Você acompanhou:
Neste caso, não importa muito. Índices não clusterizados não exclusivos armazenam colunas de chave de índice clusterizado em todos os níveis do índice não clusterizado. Veja meu post aqui: Onde as chaves de índice clusterizadas ousam .
E:
Eu geralmente não me preocupo com a fragmentação do índice, então não. Meu objetivo é criar índices para ajudar nas consultas. Um índice fragmentado é muito mais útil do que um índice inexistente e, em muitas circunstâncias, você nunca notará a fragmentação .
Espero que isto ajude!