Estou com o que considero uma estimativa de cardinalidade impossivelmente alta para a seguinte consulta:
SELECT dm.PRIMARY_ID
FROM
(
SELECT COALESCE(d1.JOIN_ID, d2.JOIN_ID, d3.JOIN_ID) PRIMARY_ID
FROM X_DRIVING_TABLE dt
LEFT OUTER JOIN X_DETAIL_1 d1 ON dt.ID = d1.ID
LEFT OUTER JOIN X_DETAIL_LINK lnk ON d1.LINK_ID = lnk.LINK_ID
LEFT OUTER JOIN X_DETAIL_2 d2 ON dt.ID = d2.ID
LEFT OUTER JOIN X_DETAIL_3 d3 ON dt.ID = d3.ID
) dm
INNER JOIN X_LAST_TABLE lst ON dm.PRIMARY_ID = lst.JOIN_ID;
O plano estimado está aqui . Estou trabalhando em uma cópia estatística das tabelas para não poder incluir um plano real. No entanto, não acho que seja muito relevante para esse problema.
O SQL Server estima que 481577 linhas serão retornadas da tabela derivada "dm". Em seguida, estima que 4528030000 linhas serão retornadas após a associação a X_LAST_TABLE, mas JOIN_ID é a chave primária de X_LAST_TIME. Eu esperaria uma estimativa de cardinalidade de junção entre 0 e 481577 linhas. Em vez disso, a estimativa de linha parece ser 10% do número de linhas que eu obteria ao unir as tabelas externa e interna. A matemática para isso funciona com o arredondamento: 481577 * 94025 * 0,1 = 45280277425, que é arredondado para 4528030000.
Estou procurando principalmente uma causa raiz para esse comportamento. Também estou interessado em soluções simples, mas não sugira alterar o modelo de dados ou usar tabelas temporárias. Esta consulta é uma simplificação da lógica em uma exibição. Eu sei que fazer COALESCE em algumas colunas e unir-se a elas não é uma boa prática. Parte do objetivo desta pergunta é descobrir se preciso recomendar que o modelo de dados seja redesenhado.
Estou testando no Microsoft SQL Server 2014 com o estimador de cardinalidade herdado ativado. O TF 4199 e outros estão ativados. Posso fornecer uma lista completa de sinalizadores de rastreamento, se isso for relevante.
Aqui está a definição de tabela mais relevante:
CREATE TABLE X_LAST_TABLE (
JOIN_ID NUMERIC(18, 0) NOT NULL
CONSTRAINT PK_X_LAST_TABLE PRIMARY KEY CLUSTERED (JOIN_ID ASC)
);
Eu também escrevi todos os scripts de criação de tabela, juntamente com suas estatísticas, se alguém quiser reproduzir o problema em um de seus servidores.
Para adicionar algumas das minhas observações, o uso do TF 2312 corrige a estimativa, mas isso não é uma opção para mim. O TF 2301 não fixa a estimativa. A remoção de uma das tabelas corrige a estimativa. Estranhamente, alterar a ordem de junção de X_DETAIL_LINK também corrige a estimativa. Alterando a ordem de junção, refiro-me a reescrever a consulta e não forçar a ordem de junção com uma dica. Aqui está um plano de consulta estimado ao alterar apenas a ordem das junções.
fonte
bigint
, em vez dedecimal(18, 0)
você poderá obter benefícios: 1) uso 8 bytes em vez de 9 para cada valor, e 2) o uso de um tipo de dados byte-comparável, em vez de um tipo de dados embalados, o que poderia ter implicações para o tempo da CPU ao comparar valores.X_DETAIL2
eX_DETAIL3
seJOIN_ID
não estiver nuloX_DETAIL1
?Respostas:
A geração de boas estimativas de cardinalidade e distribuição já é bastante difícil quando o esquema é 3NF + (com chaves e restrições) e a consulta é relacional e principalmente SPJG (seleção-projeção-junção por grupo). O modelo CE é construído sobre esses princípios. Quanto mais recursos incomuns ou não relacionais houver em uma consulta, mais se aproximará dos limites com os quais a estrutura de cardinalidade e seletividade pode lidar. Vá longe demais e a CE vai desistir e adivinhar .
A maior parte do exemplo do MCVE é SPJ simples (sem G), embora com equijoins predominantemente externos (modelados como junção interna mais anti-semijoína), em vez do equijoin interno mais simples (ou semijoína). Todas as relações têm chaves, embora nenhuma chave estrangeira ou outras restrições. Todas as uniões, exceto uma, são uma para muitas, o que é bom.
A exceção é a junção externa muitos para muitos entre
X_DETAIL_1
eX_DETAIL_LINK
. A única função dessa associação no MCVE é potencialmente duplicar linhasX_DETAIL_1
. Esse é um tipo incomum de coisa.Predicados de igualdade simples (seleções) e operadores escalares também são melhores. Por exemplo, atributo / constante de comparação igual ao atributo normalmente funciona bem no modelo. É relativamente "fácil" modificar histogramas e estatísticas de frequência para refletir a aplicação de tais predicados.
COALESCE
é construído sobreCASE
, que por sua vez é implementado internamente comoIIF
(e isso era verdade muito antes deIIF
aparecer na linguagem Transact-SQL). O CE modelaIIF
como aUNION
com dois filhos mutuamente exclusivos, cada um consistindo em um projeto em uma seleção na relação de entrada. Cada um dos componentes listados possui suporte ao modelo, portanto, combiná-los é relativamente simples. Mesmo assim, quanto mais camadas as abstrações, menos preciso o resultado final tende a ser - uma razão pela qual planos de execução maiores tendem a ser menos estáveis e confiáveis.ISNULL
, por outro lado, é intrínseco ao mecanismo. Não é construído usando mais componentes básicos. A aplicação do efeito deISNULL
um histograma, por exemplo, é tão simples quanto substituir a etapa porNULL
valores (e compactação conforme necessário). Ainda é relativamente opaco, como os operadores escalares, e é melhor evitar sempre que possível. No entanto, geralmente é mais amigável ao otimizador (menos hostil ao otimizador) do que umaCASE
alternativa baseada em.O CE (70 e 120+) é muito complexo, mesmo para os padrões do SQL Server. Não se trata de aplicar lógica simples (com uma fórmula secreta) a cada operador. O CE conhece chaves e dependências funcionais; sabe estimar usando frequências, estatísticas multivariadas e histogramas; e há uma tonelada absoluta de casos especiais, refinamentos, freios e contrapesos e estruturas de apoio. Geralmente estima, por exemplo, junções de várias maneiras (frequência, histograma) e decide um resultado ou ajuste com base nas diferenças entre os dois.
Uma última coisa básica a ser abordada: a estimativa inicial da cardinalidade é executada para todas as operações na árvore de consultas, de baixo para cima. A seletividade e a cardinalidade são derivadas primeiro para os operadores de folha (relações de base). Os histogramas modificados e as informações de densidade / frequência são derivados para os operadores principais. Quanto mais subimos na árvore, menor é a qualidade das estimativas, pois os erros tendem a se acumular.
Essa estimativa abrangente única inicial fornece um ponto de partida e ocorre bem antes de qualquer consideração ser dada a um plano de execução final (isso ocorre muito antes do estágio trivial de compilação do plano). A árvore de consultas neste momento tende a refletir bastante a forma escrita da consulta (embora com subconsultas removidas e simplificações aplicadas etc.)
Imediatamente após a estimativa inicial, o SQL Server executa o reordenamento de junção heurística, que tenta falar livremente a árvore para colocar tabelas menores e as junções de alta seletividade primeiro. Ele também tenta posicionar as junções internas antes das junções externas e cruzar produtos. Suas capacidades não são extensas; seus esforços não são exaustivos; e não considera custos físicos (já que eles ainda não existem - apenas informações estatísticas e metadados estão presentes). O reordenamento heurístico é mais bem-sucedido em simples árvores equijóicas internas. Existe para fornecer um "ponto de partida" melhor para a otimização baseada em custos.
O MCVE tem uma junção muitos-para-muitos "incomum", principalmente redundante , e uma junção equi
COALESCE
no predicado. A árvore do operador também possui uma união interna por último , cujo reordenamento de união heurística não conseguiu mover a árvore para uma posição mais preferida. Deixando de lado todos os escalares e projeções, a árvore de junção é:Observe que a estimativa final com defeito já está em vigor. É impresso
Card=4.52803e+009
e armazenado internamente como o valor de ponto flutuante de precisão dupla 4.5280277425e + 9 (4528027742.5 em decimal).A tabela derivada na consulta original foi removida e as projeções normalizadas. Uma representação SQL da árvore na qual a estimativa inicial de cardinalidade e seletividade foi realizada é:
(Além disso, o repetido
COALESCE
também está presente no plano final - uma vez no escalar computacional final e outra no lado interno da junção interna).Observe a junção final. Essa junção interna é (por definição) o produto cartesiano
X_LAST_TABLE
e a saída da junção anterior, com uma seleção (predicado de junção)lst.JOIN_ID = COALESCE(d1.JOIN_ID, d2.JOIN_ID, d3.JOIN_ID)
aplicada. A cardinalidade do produto cartesiano é simplesmente 481577 * 94025 = 45280277425.Para isso, precisamos determinar e aplicar a seletividade do predicado. A combinação da
COALESCE
árvore expandida opaca (em termos de ,UNION
eIIF
lembre-se), juntamente com o impacto nas informações-chave, histogramas e frequências derivadas da junção externa "incomum", geralmente redundante de muitos para muitos, significa que o CE é incapaz de derivar uma estimativa aceitável de qualquer maneira normal.Como resultado, ele entra na Guess Logic. A lógica de adivinhação é moderadamente complexa, com tentativas de camadas de adivinhações "instruídas" e "não tão educadas". Se não for encontrada uma base melhor para uma suposição, o modelo usa uma suposição de último recurso, que para uma comparação de igualdade é:
sqllang!x_Selectivity_Equal
= 0,1 seletividade fixa (10% de suposição):O resultado é seletividade de 0,1 no produto cartesiano: 481577 * 94025 * 0,1 = 4528027742,5 (~ 4,52803e + 009) conforme mencionado anteriormente.
Reescreve
Quando a junção problemática é comentada , uma estimativa melhor é produzida porque a seletividade fixa "palpite de último recurso" é evitada (as principais informações são retidas pelas junções 1-M). A qualidade da estimativa ainda é pouco confiável, porque um
COALESCE
predicado de junção não é nada amigável para o CE. A estimativa revisada faz pelo menos olhar mais razoável para os seres humanos, suponho.Quando a consulta é gravada com a junção externa a
X_DETAIL_LINK
última colocada , o reordenamento heurístico pode trocá-la pela junção interna final paraX_LAST_TABLE
. Colocar a junção interna logo ao lado da junção externa do problema dá às habilidades limitadas de reordenar cedo a oportunidade de melhorar a estimativa final, uma vez que os efeitos da junção externa "incomum" muitos-para-muitos "redundante" são redundantes após a complicada estimativa de seletividade paraCOALESCE
. Mais uma vez, as estimativas são um pouco melhores do que suposições fixas e provavelmente não resistiriam a um interrogatório determinado em um tribunal.Reordenar uma mistura de junções internas e externas é demorado e demorado (mesmo a otimização completa do estágio 2 tenta apenas um subconjunto limitado de movimentos teóricos).
O aninhado
ISNULL
sugerido na resposta de Max Vernon consegue evitar o palpite fixo de resgate, mas a estimativa final é de improvável zero linhas (aumentada para uma linha por decência). Isso também pode ser um palpite fixo de 1 linha, para toda a base estatística que o cálculo tiver.Essa é uma expectativa razoável, mesmo que se aceite que a estimativa de cardinalidade pode ocorrer em momentos diferentes (durante a otimização baseada em custos) em subárvores fisicamente diferentes, mas lógica e semanticamente idênticas - com o plano final sendo uma espécie de melhor costura melhor (por grupo de notas). A falta de uma garantia de consistência em todo o plano não significa que uma junção individual deva desprezar a respeitabilidade, entendo isso.
Por outro lado, se acabarmos com o palpite de último recurso , a esperança já está perdida, então, por que se preocupar? Tentamos todos os truques que conhecíamos e desistimos. Se nada mais, a estimativa final é um ótimo sinal de alerta de que nem tudo correu bem dentro do CE durante a compilação e otimização dessa consulta.
Quando tentei o MCVE, o 120+ CE produziu uma estimativa final de linha zero (= 1) (como a aninhada
ISNULL
) para a consulta original, o que é igualmente inaceitável para o meu modo de pensar.A solução real provavelmente envolve uma alteração no design, para permitir junções equitativas simples sem
COALESCE
ouISNULL
, idealmente, chaves estrangeiras e outras restrições úteis para a compilação de consultas.fonte
Acredito que o
Compute Scalar
operador resultante daCOALESCE(d1.JOIN_ID, d2.JOIN_ID, d3.JOIN_ID)
associaçãoX_LAST_TABLE.JOIN_ID
seja a causa raiz do problema. Historicamente, os escalares de computação têm sido difíceis de custar com precisão 1 , 2 .Como você forneceu um exemplo verificável minimamente completo (obrigado!) Com estatísticas precisas, sou capaz de reescrever a consulta para que a junção não exija mais a
CASE
funcionalidadeCOALESCE
expandida, resultando em estimativas de linha muito mais precisase aparentemente mais custo total precisoVeja o adendo no final. :Embora
xID IS NOT NULL
tecnicamente não seja necessário, pois o valorID = JOIN_ID
não será associado a valores nulos, eu os incluí, pois retrata com mais clareza a intenção.Plano 1 e Plano 2
Plano 1:
Plano 2:
A nova consulta se beneficia (?) Da paralelização. Observe também que a nova consulta possui um número de saída estimado de linhas 1, que pode ser pior no final do dia do que a estimativa de 4528030000 para a consulta original. O custo da subárvore para o operador de seleção na nova consulta é 243210, enquanto o original é 536.535, o que é claramente menor. Dito isto, não acredito que a primeira estimativa esteja próxima da realidade.
Adenda 1.
Após uma consulta mais aprofundada com várias pessoas no The Heap ™, estimulada por uma discussão com o @Lamak, parece que minha consulta observacional acima tem um desempenho terrível, mesmo com o paralelismo. Uma solução que permite bom desempenho e boas estimativas de cardinalidade consiste em substituir o
COALESCE(x,y,z)
por umISNULL(ISNULL(x, y), z)
, como em:COALESCE
é transformado em umaCASE
declaração "oculta" pelo otimizador de consulta. Dessa forma, o estimador de cardinalidade tem mais dificuldade em descobrir estatísticas confiáveis para colunas enterradas no interiorCOALESCE
.ISNULL
ser uma função intrínseca é muito mais "aberta" para o estimador de cardinalidade. Também não vale nada queISNULL
possa ser otimizado se o destino for conhecido como não nulo.O plano para a
ISNULL
variante se parece com:(Cole a versão do plano aqui ).
Para sua informação, apóie o Sentry One por seu ótimo Plan Explorer, que eu usei para produzir os planos gráficos acima.
fonte
De acordo com a sua condição de associação, a tabela pode ser organizada de tantas maneiras, que "mudar de uma maneira específica" corrige o resultado.
Suponha que juntar apenas uma tabela lhe dê o resultado correto.
Aqui no lugar de
X_DETAIL_1
, você pode usarX_DETAIL_2
ouX_DETAIL_3
.Por isso, o objetivo do resto 2 tabelas não é claro.
É como se você tivesse quebrado a mesa
X_DETAIL_1
em mais duas partes.Muito provavelmente " não há erro em que você está preenchendo essas tabelas. " Idealmente
X_DETAIL_1
,X_DETAIL_2
eX_DETAIL_3
deve conter a mesma quantidade de linhas.Mas uma ou mais tabelas contêm um número indesejado de linhas.
Desculpe se estou errado.
fonte