Por que a busca não é escolhida pelo otimizador
TL: DR A definição expandida da coluna computada interfere na capacidade do otimizador de reordenar uniões inicialmente. Com um ponto de partida diferente, a otimização baseada em custos segue um caminho diferente através do otimizador e termina com uma escolha de plano final diferente.
Detalhes
Para todas as consultas, exceto a mais simples, o otimizador não tenta explorar nada como todo o espaço possível de planos. Em vez disso, ele escolhe um ponto de partida de aparência razoável e gasta uma quantidade orçada de esforço explorando variações lógicas e físicas, em uma ou mais fases de pesquisa, até encontrar um plano razoável.
A principal razão pela qual você obtém planos diferentes (com diferentes estimativas de custo final) para os dois casos é que existem pontos de partida diferentes . A partir de um local diferente, a otimização termina em um local diferente (após seu número limitado de iterações de exploração e implementação). Espero que isso seja razoavelmente intuitivo.
O ponto de partida que mencionei é um pouco baseado na representação textual da consulta, mas são feitas alterações na representação interna da árvore conforme ela passa pelos estágios de análise, ligação, normalização e simplificação da compilação da consulta.
É importante ressaltar que o ponto de partida exato depende muito da ordem de junção inicial selecionada pelo otimizador. Essa escolha é feita antes do carregamento das estatísticas e antes de quaisquer estimativas de cardinalidade serem derivadas. A cardinalidade total (número de linhas) em cada tabela é no entanto conhecida, tendo sido obtida dos metadados do sistema.
A ordem de junção inicial é, portanto, baseada em heurísticas . Por exemplo, o otimizador tenta reescrever a árvore para que as tabelas menores sejam unidas antes das maiores e as junções internas antes das junções externas (e junções cruzadas).
A presença da coluna computada interfere nesse processo, mais especificamente na capacidade do otimizador de empurrar as junções externas para baixo na árvore de consultas. Isso ocorre porque a coluna computada é expandida em sua expressão subjacente antes que ocorra a reordenação da junção, e mover uma junção após uma expressão complexa é muito mais difícil do que movê-la para uma referência de coluna simples.
As árvores envolvidas são muito grandes, mas para ilustrar, a árvore de consulta inicial da coluna não computada começa com: (observe as duas junções externas na parte superior)
LogOp_Select
LogOp_Apply (x_jtLeftOuter)
LogOp_LeftOuterJoin
LogOp_NAryJoin
LogOp_LeftAntiSemiJoin
LogOp_NAryJoin
LogOp_Get TBL: dbo.table1 (também conhecido por TBL: a4)
LogOp_Select
LogOp_Get TBL: dbo.table6 (também conhecido por TBL: a3)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [a3] .col18
ScaOp_Const TI (agrupamento de varchar 53256, Var, Trim, ML = 16)
LogOp_Select
LogOp_Get TBL: dbo.table1 (também conhecido por TBL: a1)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [a1] .col2
ScaOp_Const TI (agrupamento de varchar 53256, Var, Trim, ML = 16)
LogOp_Select
LogOp_Get TBL: dbo.table5 (também conhecido por TBL: a2)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [a2] .col2
ScaOp_Const TI (agrupamento de varchar 53256, Var, Trim, ML = 16)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [a4] .col2
ScaOp_Identifier QCOL: [a3] .col19
LogOp_Select
LogOp_Get TBL: dbo.table7 (também conhecido por TBL: a7)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [a7] .col22
ScaOp_Const TI (agrupamento de varchar 53256, Var, Trim, ML = 16)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [a4] .col2
ScaOp_Identifier QCOL: [a7] .col23
LogOp_Select
LogOp_Get TBL: tabela1 (também conhecido por TBL: cdc)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [cdc] .col6
ScaOp_Const TI (smallint, ML = 2) XVAR (smallint, Não Possuído, Valor = 4)
LogOp_Get TBL: dbo.table5 (também conhecido por TBL: a5)
LogOp_Get TBL: tabela2 (também conhecido por TBL: cdt)
ScaOp_Logical x_lopAnd
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [a5] .col2
ScaOp_Identifier QCOL: [cdc] .col2
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [a4] .col2
ScaOp_Identifier QCOL: [cdc] .col2
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [cdt] .col1
ScaOp_Identifier QCOL: [cdc] .col1
LogOp_Get TBL: tabela3 (também conhecido por TBL: ahcr)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [ahcr] .col9
ScaOp_Identifier QCOL: [cdt] .col1
O mesmo fragmento da consulta da coluna computada é: (observe a junção externa muito mais abaixo, a definição expandida da coluna computada e algumas outras diferenças sutis na ordem de junção (interna))
LogOp_Select
LogOp_Apply (x_jtLeftOuter)
LogOp_NAryJoin
LogOp_LeftAntiSemiJoin
LogOp_NAryJoin
LogOp_Get TBL: dbo.table1 (também conhecido por TBL: a4)
LogOp_Select
LogOp_Get TBL: dbo.table6 (também conhecido por TBL: a3)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [a3] .col18
ScaOp_Const TI (agrupamento de varchar 53256, Var, Trim, ML = 16)
LogOp_Select
LogOp_Get TBL: dbo.table1 (também conhecido por TBL: a1
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [a1] .col2
ScaOp_Const TI (agrupamento de varchar 53256, Var, Trim, ML = 16)
LogOp_Select
LogOp_Get TBL: dbo.table5 (também conhecido por TBL: a2)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [a2] .col2
ScaOp_Const TI (agrupamento de varchar 53256, Var, Trim, ML = 16)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [a4] .col2
ScaOp_Identifier QCOL: [a3] .col19
LogOp_Select
LogOp_Get TBL: dbo.table7 (também conhecido por TBL: a7)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [a7] .col22
ScaOp_Const TI (agrupamento de varchar 53256, Var, Trim, ML = 16)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [a4] .col2
ScaOp_Identifier QCOL: [a7] .col23
LogOp_Project
LogOp_LeftOuterJoin
LogOp_Join
LogOp_Select
LogOp_Get TBL: tabela1 (também conhecido por TBL: cdc)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [cdc] .col6
ScaOp_Const TI (smallint, ML = 2) XVAR (smallint, Não Possuído, Valor = 4)
LogOp_Get TBL: tabela2 (também conhecido por TBL: cdt)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [cdc] .col1
ScaOp_Identifier QCOL: [cdt] .col1
LogOp_Get TBL: table3 (também conhecido por TBL: ahcr)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [ahcr] .col9
ScaOp_Identifier QCOL: [cdt] .col1
AncOp_PrjList
QCOL AncOp_PrjEl: [cdc] .col7
ScaOp_Convert char collate 53256, Null, Trim, ML = 6
ScaOp_IIF varchar agrupar 53256, Nulo, Var, Trim, ML = 6
ScaOp_Comp x_cmpEq
ScaOp_Intrinsic isnumeric
ScaOp_Intrinsic right
ScaOp_Identifier QCOL: [cdc] .col4
ScaOp_Const TI (int, ML = 4) XVAR (int, Não possuído, Valor = 4)
ScaOp_Const TI (int, ML = 4) XVAR (int, Não possuído, Valor = 0)
ScaOp_Const TI (varchar agrupar 53256, Var, Trim, ML = 1) XVAR (varchar, Propriedade, Valor = Len, Dados = (0,))
ScaOp_Instrinsic substring
ScaOp_Const TI (int, ML = 4) XVAR (int, Não possuído, Valor = 6)
ScaOp_Const TI (int, ML = 4) XVAR (int, Não possuído, Valor = 1)
ScaOp_Identifier QCOL: [cdc] .col4
LogOp_Get TBL: dbo.table5 (também conhecido por TBL: a5)
ScaOp_Logical x_lopAnd
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [a5] .col2
ScaOp_Identifier QCOL: [cdc] .col2
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [a4] .col2
ScaOp_Identifier QCOL: [cdc] .col2
As estatísticas são carregadas e uma estimativa de cardinalidade inicial é executada na árvore logo após a ordem de junção inicial ser definida. Ter as junções em ordens diferentes também afeta essas estimativas e, portanto, tem um efeito indireto durante a otimização posterior baseada em custos.
Finalmente, para esta seção, ter uma junção externa presa no meio da árvore pode impedir que outras regras de reordenação de junções sejam correspondidas durante a otimização baseada em custos.
O uso de um guia de plano (ou, equivalentemente, uma USE PLAN
dica - exemplo para sua consulta ) altera a estratégia de pesquisa para uma abordagem mais orientada a objetivos, guiada pela forma geral e pelos recursos do modelo fornecido. Isso explica por que o otimizador pode encontrar o mesmo table1
plano de busca nos esquemas de colunas calculados e não calculados, quando um guia ou dica de plano é usada.
Se podemos fazer algo diferente para fazer a busca acontecer
Isso é algo com o qual você só precisa se preocupar se o otimizador não encontrar um plano com características de desempenho aceitáveis por conta própria.
Todas as ferramentas normais de ajuste são potencialmente aplicáveis. Você pode, por exemplo, dividir a consulta em partes mais simples, revisar e melhorar a indexação disponível, atualizar ou criar novas estatísticas ... e assim por diante.
Todas essas coisas podem afetar as estimativas de cardinalidade, o caminho do código adotado pelo otimizador e influenciar as decisões baseadas em custos de maneiras sutis.
Você pode finalmente usar dicas (ou um guia de plano), mas essa não é geralmente a solução ideal.
Perguntas adicionais dos comentários
Concordo que é melhor simplificar a consulta etc., mas existe uma maneira (sinalizador de rastreamento) de fazer o otimizador continuar com a otimização e alcançar o mesmo resultado?
Não, não há sinalizador de rastreamento para executar uma pesquisa exaustiva e você não deseja um. O possível espaço de pesquisa é vasto e os tempos de compilação que excedem a idade do universo não seriam bem recebidos. Além disso, o otimizador não conhece todas as transformações lógicas possíveis (ninguém sabe).
Além disso, por que a expansão complexa é necessária, pois a coluna é mantida? Por que o otimizador não pode evitar expandi-lo, tratá-lo como uma coluna comum e alcançar o mesmo ponto de partida?
As colunas computadas são expandidas (como as visualizações) para permitir oportunidades adicionais de otimização. A expansão pode corresponder, por exemplo, a uma coluna ou índice persistente posteriormente no processo, mas isso ocorre depois que a ordem de junção inicial é corrigida.