Eu estava trabalhando em uma consulta no trabalho, que tinha uma junção à esquerda como
cast(cola as varchar) + '-' + right('000' + cast(colb as varchar), 3) = x
O plano de execução real para esta consulta foi bastante próximo, est 269 vs, na verdade, 475.
Alterar o +preenchimento certo para usar o formato (colb, '000') resulta em uma enorme estimativa incorreta do número de linhas, em pelo menos 4 milhões, o que faz com que a consulta demore de 10 a 15 vezes mais.
Entendo por que uma estimativa incorreta causaria um problema, mas não entendo por que o uso de Formato causaria uma estimativa menos precisa.
FORMAT
retornanvarchar
que tem uma precedência de tipo de dados mais alta do que a coluna varchar comparada. Além da estimativa imprecisa da contagem de linhas, a conversão implícita davarchar
coluna comparada paranvarchar
impedirá que os índices dessa coluna sejam usados com eficiênciaTente transmitir o
FORMAT
resultado paravarchar
.Há algumas coisas acontecendo aqui:
Os fatores envolvidos são:
VARCHAR
, com um agrupamento do SQL Server, comparado aosNVARCHAR
dados (Observação: este cenário é específico do tipo de agrupamento: se o agrupamento fosse um agrupamento do Windows, não haveria degradação discernível de desempenho. Para obter detalhes, consulte " Impacto nos índices Ao misturar os tipos VARCHAR e NVARCHAR ")WITH FULLSCAN
não tem efeito)Os três fatores mencionados acima foram confirmados por meio de testes (veja abaixo). Dois dos três fatores são fáceis de corrigir:
NVARCHAR
valores paraVARCHAR
se desejar usar umaVARCHAR
coluna indexada OU altere o Collation da coluna para um Collation do Windows.REBUILD
do(s) índice(s). Fazer umALTER INDEX ... REORGANIZE;
ouUPDATE STATISTICS ... WITH FULLSCAN;
por si só não parece ajudar (pelo menos em termos de contagem de linhas estimadas).CASE / CONVERT
+RIGHT
é mais eficiente queFORMAT
, AND produz o mesmo resultado, então useCASE / CONVERT
+RIGHT
;FORMAT
pode fazer algumas coisas bacanas, mas é desnecessário para preenchimento à esquerda).Tenha também em mente as prioridades. Embora seja ideal ter contagens de linhas estimadas precisas, se elas estiverem próximas o suficiente, você ficará bem. Ou seja, não sinta a necessidade de fazer um trabalho extra para obter contagens de linhas estimadas super precisas se isso não proporcionar nenhum ganho real de desempenho (especialmente porque, dependendo do nível de fragmentação, a função não determinística às vezes tem um estimativa de linha mais precisa!). Por outro lado, alterar o tipo de dados (do valor que está sendo comparado) ou Collation vale o esforço, pois isso terá um impacto positivo perceptível. Então, fazer um
REBUILD
do índice o aproximará o suficiente das contagens de linhas estimadas.Método de teste
Eu testei isso preenchendo uma tabela temporária local com 5 milhões de linhas da coluna "name" de
sys.all_objects
(e usando um Collation ofSQL_Latin1_General_CP1_CI_AS
), criando um índice não clusterizado na coluna de string e adicionando outras 100k linhas para fragmentar o índice .Eu filtrei um
VARCHAR
literal e, em seguida, o mesmo literal de string, mas prefixado com um "N" maiúsculo para torná-loNVARCHAR
. Isso isolou a questão do tipo de dados do valor de comparação.Em seguida, filtrei o mesmo valor literal, mas envolto em uma chamada para
FORMAT
. Isso isolou a questão das funções não determinísticas.Para confirmar o efeito comportamental do determinismo de função, criei duas funções SQLCLR que nada mais faziam do que retornar os valores passados, mas uma é determinística e a outra não. Isso deixa claro que a questão é determinismo e nada mais acontecendo com a função. Eu usei SQLCLR porque não parece haver uma maneira equivalente de fazer isso em T-SQL. Mesmo que a função seja marcada no sistema como sendo determinística (criando a UDF usando
WITH SCHEMABINDING
), o comportamento espelhará o de funções não determinísticas (eu testei isso, mas não o incluí abaixo).Eu usei
SET STATISTICS IO, TIME ON;
e marquei a opção "Incluir Plano de Execução Real" no SSMS.Depois de executar o primeiro conjunto de testes, executei:
e re-executou os testes. Melhoria mínima nas leituras lógicas e nenhuma alteração no número estimado de linhas.
Executei então:
e re-executou os testes. Nenhuma alteração no número estimado de linhas.
Executei então:
e , finalmente , vimos uma melhoria nas leituras lógicas e no número estimado de linhas.
Em seguida , larguei a tabela, recriei-a usando
Latin1_General_100_CI_AS_SC
como Collation e executei novamente os testes conforme descrito acima.Código de teste
Código SQLCLR
O código a seguir foi usado para criar duas funções escalares que fazem exatamente a mesma coisa: simplesmente retornam o valor passado. A única diferença entre as duas funções é que uma está marcada como
IsDeterministic = true
e a outra está marcada comoIsDeterministic = false
.Configuração de teste
Os testes (e resultados)
Chave de resultados:
SQL_Latin1_General_CP1_CI_AS
)Latin1_General_100_CI_AS_SC
)REBUILD
} / { depoisREBUILD
}Segunda Variação
A seção de comentários para FORMAT (Transact-SQL) diz
Portanto, o planejador de consulta está confuso sobre o que esperar como resultado dessa função. Talvez o comportamento não determinístico até o impeça de aplicar algumas otimizações, como armazenar em cache resultados intermediários.