Esta pergunta é semelhante a Otimizando a pesquisa por intervalo de IP? mas esse é restrito ao SQL Server 2000.
Suponha que eu tenha 10 milhões de intervalos armazenados provisoriamente em uma tabela estruturada e preenchida como abaixo.
CREATE TABLE MyTable
(
Id INT IDENTITY PRIMARY KEY,
RangeFrom INT NOT NULL,
RangeTo INT NOT NULL,
CHECK (RangeTo > RangeFrom),
INDEX IX1 (RangeFrom,RangeTo),
INDEX IX2 (RangeTo,RangeFrom)
);
WITH RandomNumbers
AS (SELECT TOP 10000000 ABS(CRYPT_GEN_RANDOM(4)%100000000) AS Num
FROM sys.all_objects o1,
sys.all_objects o2,
sys.all_objects o3,
sys.all_objects o4)
INSERT INTO MyTable
(RangeFrom,
RangeTo)
SELECT Num,
Num + 1 + CRYPT_GEN_RANDOM(1)
FROM RandomNumbers
Eu preciso conhecer todos os intervalos que contêm o valor 50,000,000
. Eu tento a seguinte consulta
SELECT *
FROM MyTable
WHERE 50000000 BETWEEN RangeFrom AND RangeTo
O SQL Server mostra que havia 10.951 leituras lógicas e quase 5 milhões de linhas foram lidas para retornar as 12 correspondentes.
Posso melhorar esse desempenho? Qualquer reestruturação da tabela ou índices adicionais é boa.
sql-server
optimization
Martin Smith
fonte
fonte
contains
consultas e, embora funcionem bem em reduzir a quantidade de dados lidos, parecem adicionar outros sobrecarga que neutraliza isso.Respostas:
O columnstore é muito útil aqui em comparação com um índice não clusterizado que varre metade da tabela. Um índice columnstore não clusterizado fornece a maior parte do benefício, mas a inserção de dados ordenados em um índice columnstore clusterizado é ainda melhor.
Por design, posso obter a eliminação de grupos de linhas na
RangeFrom
coluna, o que eliminará metade dos meus grupos de linhas. Mas, devido à natureza dos dados, também recebo a eliminação de grupos de linhas naRangeTo
coluna:Para tabelas maiores com mais dados variáveis, existem diferentes maneiras de carregar os dados para garantir a melhor eliminação possível de grupos de linhas nas duas colunas. Para seus dados em particular, a consulta leva 1 ms.
fonte
Paul White apontou uma resposta para uma pergunta semelhante contendo um link para um artigo interessante de Itzik Ben Gan . Isso descreve o modelo "Árvore de intervalo relacional estático" que permite que isso seja feito com eficiência.
Em resumo, essa abordagem envolve o armazenamento de um valor calculado ("forknode") com base nos valores de intervalo na linha. Ao pesquisar intervalos que cruzam outro intervalo, é possível pré-calcular os possíveis valores de código de código que as linhas correspondentes devem ter e usá-lo para encontrar os resultados com um máximo de 31 operações de busca (o abaixo suporta números inteiros no intervalo de 0 a o máximo assinado 32 bit int)
Com base nisso, reestruturei a tabela como abaixo.
E, em seguida, usou a seguinte consulta (o artigo está procurando intervalos de interseção, portanto, encontrar um intervalo contendo um ponto é um caso degenerado disso)
Isso normalmente é executado na
1ms
minha máquina quando todas as páginas estão em cache - com estatísticas de E / S.e planejar
NB: A fonte usa TVFs com várias instruções em vez de uma CTE recursiva para obter a adesão dos nós, mas no interesse de tornar minha resposta independente, eu optei pela última. Para uso em produção, eu provavelmente usaria os TVFs.
fonte
Consegui encontrar uma abordagem de modo de linha que seja competitiva com a abordagem N / CCI, mas você precisa saber algo sobre seus dados. Suponha que você tinha uma coluna que continha a diferença de
RangeFrom
eRangeTo
e indexado-lo junto comRangeFrom
:Se você soubesse todos os valores distintos de
DiffOfColumns
, poderia realizar uma pesquisa para cada valorDiffOfColumns
com um filtro de intervalo ativadoRangeTo
para obter todos os dados relevantes. Por exemplo, se soubermos queDiffOfColumns
= 2, os únicos valores permitidos paraRangeFrom
são 49999998, 49999999 e 50000000. A recursão pode ser usada para obter todos os valores distintosDiffOfColumns
e funciona bem para o seu conjunto de dados, porque existem apenas 256 deles. A consulta abaixo leva cerca de 6 ms na minha máquina:Você pode ver a parte recursiva usual junto com a busca do índice para cada valor distinto:
A falha nessa abordagem é que ela começa a ficar lenta quando há muitos valores distintos
DiffOfColumns
. Vamos fazer o mesmo teste, mas use emCRYPT_GEN_RANDOM(2)
vez deCRYPT_GEN_RANDOM(1)
.A mesma consulta agora encontra 65536 linhas da parte recursiva e ocupa 823 ms de CPU na minha máquina. Há PAGELATCH_SH espera e outras coisas ruins acontecendo. Eu posso melhorar o desempenho agrupando os valores diff para manter o número de valores exclusivos sob controle e ajustando-os para o agrupamento no
CROSS APPLY
. Para este conjunto de dados, tentarei 256 blocos:Uma maneira de evitar a obtenção de linhas extras (agora estou comparando com um valor arredondado em vez do valor verdadeiro) é filtrando
RangeTo
:A consulta completa agora leva 6 ms na minha máquina.
fonte
Uma maneira alternativa de representar um intervalo seria como pontos em uma linha.
A seguir, migra todos os dados para uma nova tabela com o intervalo representado como um
geometry
tipo de dados.A consulta equivalente para encontrar intervalos contendo o valor
50,000,000
está abaixo.As leituras para isso mostram uma melhoria na
10,951
consulta original.No entanto, não há melhorias significativas em relação ao original em termos de tempo decorrido . Os resultados típicos da execução são 250 ms vs 252 ms.
O plano de execução é mais complexo como abaixo
O único caso em que a reescrita apresenta um desempenho melhor para mim é com um cache frio.
Tão decepcionante neste caso e difícil recomendar essa reescrita, mas a publicação de resultados negativos também pode ser útil.
fonte
Como uma homenagem aos nossos novos senhores de robôs, decidi ver se alguma das novas funcionalidades R e Python poderia nos ajudar aqui. A resposta é não, pelo menos para os scripts que eu poderia trabalhar e retornar resultados corretos. Se alguém com melhor conhecimento aparecer, fique à vontade para me bater. Minhas taxas são razoáveis.
Para fazer isso, configurei uma VM com 4 núcleos e 16 GB de RAM, pensando que isso seria suficiente para lidar com um conjunto de dados de ~ 200 MB.
Vamos começar com o idioma que não existe em Boston!
R
Este foi um momento ruim.
O plano de execução é bastante desinteressante, embora eu não saiba por que o operador do meio precisa nos chamar de nomes.
Em seguida, codificando com giz de cera!
Pitão
Apenas quando você pensou que não poderia ficar pior do que R:
Outro plano de execução de boca suja :
Hmm e Hmmer
Até agora, não estou impressionado. Mal posso esperar para excluir esta VM.
fonte
DECLARE @input INT = 50000001; EXEC dbo.sp_execute_external_script @language = N'R', @script = N'OutputDataSet <- InputDataSet[which(x >= InputDataSet$RangeFrom & x <= InputDataSet$RangeTo) , ]', @parallel = 1, @input_data_1 = N'SELECT Id, RangeFrom, RangeTo FROM dbo.MyTable;', @params = N'@x INT', @x = 50000001 WITH RESULT SETS ( ( Id INT NOT NULL, RangeFrom INT NOT NULL, RangeTo INT NOT NULL ));
mas sim, o desempenho não é ótimo. Eu uso R para coisas que você não pode fazer no SQL, digamos, se você quisesse prever alguma coisa.Encontrei uma solução muito boa usando uma coluna computada, no entanto, é apenas válida para um único valor. Dito isto, se você tem um valor mágico, talvez seja o suficiente.
Começando com a amostra especificada e modificando a tabela:
A consulta simplesmente se torna:
O que retorna os mesmos resultados que sua consulta inicial. Com os planos de execução desativados, eis as estatísticas (truncadas por questões de brevidade):
E aqui está o plano de consulta :
fonte
WHERE (50000000 BETWEEN RangeFrom AND RangeTo) INCLUDE (..)
?CREATE INDEX IX1_redo ON dbo.MyTable (curtis_jackson) INCLUDE (RangeFrom, RangeTo) WHERE RangeTo >= 50000000 AND RangeFrom <= 50000000
podia funcionar. E a consulta aSELECT * FROM MyTable WHERE RangeTo >= 50000000 AND RangeFrom <= 50000000;
usa - então não há muita necessidade de Curtis pobreMeu solução baseia-se na observação de que o intervalo de tem um máximo conhecida largura W . Para os dados de amostra, esse é um byte ou 256 inteiros. Daí para uma determinada pesquisa valor do parâmetro P sabemos o menor RangeFrom que podem estar no conjunto de resultados é P - W . Adicionar isso ao predicado fornece
Dada a configuração original e a consulta à minha máquina (Windows 10 de 64 bits, i7 hyperthreaded de 4 núcleos, 2,8 GHz, 16 GB de RAM) retorna 13 linhas. Essa consulta usa uma busca de índice paralelo do índice (RangeFrom, RangeTo). A consulta revisada também realiza uma busca de índice paralelo no mesmo índice.
As medidas para as consultas originais e revisadas são
Para a consulta original, o número de linhas lidas é igual ao número de linhas que são menores ou iguais a @P. O otimizador de consulta (QO) não tem alternativa, mas lê todas elas, pois não pode determinar antecipadamente quais dessas linhas satisfarão o predicado. O índice de várias colunas em (RangeFrom, RangeTo) não é útil para eliminar linhas que não correspondem a RangeTo, pois não há correlação entre a primeira chave de índice e a segunda que pode ser aplicada. Por exemplo, a primeira linha pode ter um pequeno intervalo e ser eliminada, enquanto a segunda linha tem um grande intervalo e é retornada, ou vice-versa.
Em uma tentativa fracassada, tentei fornecer essa certeza através de uma restrição de verificação:
Não fez diferença.
Ao incorporar meu conhecimento externo da distribuição de dados ao predicado, posso fazer com que o QO pule as linhas RangeFrom de baixo valor, que nunca podem fazer parte do conjunto de resultados, e passe a coluna principal do índice para as linhas admissíveis. Isso é mostrado no predicado de busca diferente para cada consulta.
Em um argumento espelho do limite superior de RangeTo é P + W . Isso não é útil, no entanto, porque não há correlação entre RangeFrom e RangeTo que permitiria à coluna à direita de um índice de várias colunas eliminar linhas. Portanto, não há benefício em adicionar esta cláusula à consulta.
Essa abordagem ganha a maior parte de seu benefício com o pequeno tamanho do intervalo. À medida que o tamanho do intervalo possível aumenta, o número de linhas de baixo valor ignoradas diminui, embora algumas ainda sejam ignoradas. No caso limitativo, com um intervalo tão grande quanto o intervalo de dados, essa abordagem não é pior que a consulta original (que é um conforto frio, admito).
Peço desculpas por quaisquer erros pontuais que possam existir nesta resposta.
fonte