Como as estimativas de linha podem ser aprimoradas para reduzir as chances de derramamentos em tempdb

11

Percebo que, quando há eventos spill to tempdb (causando consultas lentas), muitas vezes as estimativas de linha estão longe de uma associação específica. Eu já vi eventos de derramamento ocorrerem com junções de mesclagem e hash, e eles geralmente aumentam o tempo de execução de 3x para 10x. Esta pergunta diz respeito a como melhorar as estimativas de linha, supondo que reduzirá as chances de eventos de derramamento.

Número real de linhas 40k.

Para esta consulta, o plano mostra uma estimativa de linha incorreta (11,3 linhas):

select Value
  from Oav.ValueArray
 where ObjectId = (select convert(bigint, Value) NodeId
                     from Oav.ValueArray
                    where PropertyId = 3331  
                      and ObjectId = 3540233
                      and Sequence = 2)
   and PropertyId = 2840
option (recompile);

Para esta consulta, o plano mostra uma boa estimativa de linha (56k linhas):

declare @a bigint = (select convert(bigint, Value) NodeId
                       from Oav.ValueArray
                      where PropertyId = 3331
                        and ObjectId = 3540233
                        and Sequence = 2);

select Value
  from Oav.ValueArray
 where ObjectId = @a               
   and PropertyId = 2840
option (recompile);

É possível adicionar estatísticas ou dicas para melhorar as estimativas de linha para o primeiro caso? Tentei adicionar estatísticas com valores de filtro específicos (propriedade = 2840), mas não foi possível obter a combinação correta ou talvez esteja sendo ignorada porque o ObjectId é desconhecido no momento da compilação e pode estar escolhendo uma média entre todos os ObjectIds.

Existe algum modo em que ele faça a consulta do probe primeiro e depois o use para determinar as estimativas de linha ou deve voar às cegas?

Essa propriedade em particular possui muitos valores (40k) em alguns objetos e zero na grande maioria. Eu ficaria feliz com uma dica em que o número máximo esperado de linhas para uma determinada junção poderia ser especificado. Esse é um problema geralmente assustador, porque alguns parâmetros podem ser determinados dinamicamente como parte da junção ou seriam melhor colocados em uma exibição (sem suporte para variáveis).

Existem parâmetros que podem ser ajustados para minimizar as chances de vazamentos para tempdb (por exemplo, min de memória por consulta)? O plano robusto não teve efeito na estimativa.

Edite 2013.11.06 : resposta a comentários e informações adicionais:

Aqui estão as imagens do plano de consulta. Os avisos são sobre o predicado de cardinalidade / busca com o convert ():

insira a descrição da imagem aqui insira a descrição da imagem aqui

Pelo comentário de @Aaron Bertrand, tentei substituir o convert () como um teste:

create table Oav.SeekObject (
       LookupId bigint not null primary key,
       ObjectId bigint not null
);

insert into Oav.SeekObject (
   LookupId, ObjectId
) VALUES (
   1, 3540233
) 

select Value
  from Oav.ValueArray
 where ObjectId = (select ObjectId 
                     from Oav.SeekObject 
                    where LookupId = 1)
   and PropertyId = 2840
option (recompile);

insira a descrição da imagem aqui

Como um ponto de interesse estranho, mas bem-sucedido, também permitiu um curto-circuito na pesquisa:

select Value
  from Oav.ValueArray
 where ObjectId = (select ObjectId 
                     from Oav.ValueArray
                    where PropertyId = 2840
                      and ObjectId = 3540233
                      and Sequence = 2)
   and PropertyId = 2840
option (recompile);

insira a descrição da imagem aqui

Ambos listam uma pesquisa de chave adequada, mas apenas os primeiros listam uma "Saída" do ObjectId. Eu acho que isso indica que o segundo é realmente um curto-circuito?

Alguém pode verificar se as análises de linha única são executadas para ajudar nas estimativas de linha? Parece errado limitar a otimização a apenas estimativas de histograma quando uma pesquisa de PK de linha única pode melhorar bastante a precisão da pesquisa no histograma (especialmente se houver histórico ou potencial de derramamento). Quando existem 10 dessas sub-junções em uma consulta real, o ideal seria que elas acontecessem em paralelo.

Uma observação: como o sql_variant armazena seu tipo base (SQL_VARIANT_PROPERTY = BaseType) dentro do próprio campo, eu esperaria que um convert () fosse praticamente gratuito, desde que seja conversível "diretamente" (por exemplo, não string para decimal, mas sim int) int ou talvez int para bigint). Como isso não é conhecido no momento da compilação, mas pode ser conhecido pelo usuário, talvez uma função "AssumeType (type, ...)" para sql_variants permita que eles sejam tratados com mais transparência.

crokusek
fonte
1
Um primeiro palpite seria que a conversão para bigint está descartando suas estimativas (o plano de consulta terá um aviso sobre isso no SQL Server 2012), mas por outro lado, sua subconsulta nunca poderá retornar nada além de 0 ou 1 linhas para uma consulta bem-sucedida. Seria interessante ver os planos de consulta que você possui. Talvez como um link para a versão XML.
Mikael Eriksson
2
O que você ganha ao ter a subconsulta embutida? Eu sugiro que retirá-lo separadamente seja mais claro no geral e, como leva a melhores estimativas, por que não usar esse método?
Aaron Bertrand
2
Qual versão do SQL Server? Você pode fornecer DDL de tabela e índice e blobs de estatística (coluna única e múltipla) para as tabelas para que possamos ver os detalhes do problema? Dividir a consulta usando o declare @a bigint = que você fez parece uma solução natural para mim, por que isso é inaceitável?
Paul White 9
2
Eu acho que seu design é um design EAV (muito simplista) que o força a usar CONVERT()em colunas e depois se juntar a eles. Isso certamente não é eficiente, na maioria dos casos. Neste particular, é apenas um valor a ser convertido, o que provavelmente não é um problema, mas que índices você tem na tabela? Os projetos de EAV geralmente têm bom desempenho, apenas com a indexação adequada (o que significa muitos índices nas tabelas geralmente estreitas).
precisa saber é o seguinte
@ Paul White, no que diz respeito à divisão ... tudo bem como uma solução para este caso. Mas, para os mais gerais / complexos, não quero abrir mão da paralelização e da legibilidade. Digamos que eu tenha 10 delas como subconsultas (algumas sendo mais complexas) em uma consulta, mas apenas 5 precisam estar "maduras" antes que o restante da consulta possa iniciar - gostaria de evitar descobrir quais 5 são essas.
crokusek

Respostas:

7

Não vou comentar sobre derramamentos, tempdb ou dicas, porque a consulta parece bem simples e precisa de muita consideração. Acho que o otimizador do SQL-Server fará seu trabalho muito bem, se houver índices adequados para a consulta.

E sua divisão em duas consultas é boa, pois mostra quais índices serão úteis. A primeira parte:

(select convert(bigint, Value) NodeId
 from Oav.ValueArray
 where PropertyId = 3331  
   and ObjectId = 3540233
   and Sequence = 2)

precisa de um índice para (PropertyId, ObjectId, Sequence)incluir o Value. Eu faria isso UNIQUEseguro. A consulta geraria erro de qualquer maneira durante o tempo de execução, se mais de uma linha fosse retornada; portanto, é bom garantir com antecedência que isso não aconteça, com o índice exclusivo:

CREATE UNIQUE INDEX
    PropertyId_ObjectId_Sequence_UQ
  ON Oav.ValueArray
    (PropertyId, ObjectId, Sequence) INCLUDE (Value) ;

A segunda parte da consulta:

select Value
  from Oav.ValueArray
 where ObjectId = @a               
   and PropertyId = 2840

precisa de um índice para (PropertyId, ObjectId)incluir Value:

CREATE INDEX
    PropertyId_ObjectId_IX
  ON Oav.ValueArray
    (PropertyId, ObjectId) INCLUDE (Value) ;

Se a eficiência não for aprimorada ou esses índices não forem usados ​​ou ainda houver diferenças nas estimativas de linha aparecendo, será necessário examinar mais detalhadamente esta consulta.

Nesse caso, as conversões (necessárias do design do EAV e o armazenamento de diferentes tipos de dados nas mesmas colunas) são uma causa provável e sua solução de divisão (como comentam @AAron Bertrand e @Paul White) a consulta em duas partes parece natural e o caminho a percorrer. Uma reformulação para ter tipos de dados diferentes em suas respectivas colunas pode ser outra.

ypercubeᵀᴹ
fonte
As tabelas tinham índices de cobertura - eu deveria ter afirmado isso na pergunta. O exemplo é realmente uma sub junção que alimenta uma consulta maior e é por isso que existe todo o barulho sobre derramamentos de tempdb.
crokusek
5

Como resposta parcial à pergunta explícita sobre como melhorar as estatísticas ...

Observe que as estimativas de linha, mesmo para o caso separado separadamente, ainda são 10X (4k vs 40k esperado).

O histograma de estatísticas provavelmente foi espalhado muito fino para essa propriedade porque é uma tabela longa (vertical) de 3,5 milhões de linhas e essa propriedade específica é extremamente esparsa.

Crie uma estatística adicional (um pouco redundante com as estatísticas IX) para a propriedade sparse:

create statistics [STAT_ValueArray_ObjPropValue_WhereProp2840] ON [Oav].[ValueArray](ObjectId, PropertyId, Value)
where PropertyId = 2840

Os originais:

insira a descrição da imagem aqui insira a descrição da imagem aqui

Com convert () removido (adequado):

insira a descrição da imagem aqui

Com convert () removido (curto-circuito):

insira a descrição da imagem aqui

Ainda está desativado em ~ 2X, provavelmente porque> 99,9% dos objetos não têm a Propriedade 2840 definida neles. De fato, apenas para este caso de teste, a propriedade existe apenas em um dos 200k Objetos distintos da tabela de linhas de 3.5M. É incrível que tenha chegado tão perto realmente. Ajustando o filtro para ter menos ObjectIds,

create statistics [STAT_ValueArray_ObjPropValue_WhereProp2840] ON [Oav].[ValueArray](ObjectId, PropertyId, Value)
where PropertyId = 2840 and ObjectId >= 3540000 and ObjectId < 3541000

create statistics [STAT_ValueArray_ObjPropValue_WhereProp2840] ON [Oav].[ValueArray](ObjectId, PropertyId, Value)
where PropertyId = 2840 and ObjectId = 3540233;

Hmm, nenhuma alteração ... Apoiou isso adicionado "com verificação completa" ao final das estatísticas (pode ser por isso que os dois anteriores não funcionaram) e sim:

create statistics [STAT_ValueArray_ObjPropValue_WhereProp2840] ON [Oav].[ValueArray](ObjectId, PropertyId, Value)
where PropertyId = 2840 with full scan;

insira a descrição da imagem aqui

Yay. Portanto, em uma tabela altamente vertical com um IX abrangente, a adição de estatísticas filtradas adicionais parece ser uma grande melhoria (principalmente para combinações de teclas esparsas, mas altamente variantes).

crokusek
fonte
Um link para alguns problemas problemáticos com estatísticas de várias colunas.
precisa saber é o seguinte