Eu tenho uma tabela de dados SQL com a seguinte estrutura:
CREATE TABLE Data(
Id uniqueidentifier NOT NULL,
Date datetime NOT NULL,
Value decimal(20, 10) NULL,
RV timestamp NOT NULL,
CONSTRAINT PK_Data PRIMARY KEY CLUSTERED (Id, Date)
)
O número de IDs distintos varia de 3000 a 50000.
O tamanho da tabela varia até mais de um bilhão de linhas.
Um ID pode cobrir entre algumas linhas até 5% da tabela.
A única consulta mais executada nesta tabela é:
SELECT Id, Date, Value, RV
FROM Data
WHERE Id = @Id
AND Date Between @StartDate AND @StopDate
Agora preciso implementar a recuperação incremental de dados em um subconjunto de IDs, incluindo atualizações.
Em seguida, usei um esquema de solicitação no qual o chamador fornece uma versão específica da linha, recupera um bloco de dados e usa o valor máximo da versão da linha dos dados retornados para a chamada subsequente.
Eu escrevi este procedimento:
CREATE TYPE guid_list_tbltype AS TABLE (Id uniqueidentifier not null primary key)
CREATE PROCEDURE GetData
@Ids guid_list_tbltype READONLY,
@Cursor rowversion,
@MaxRows int
AS
BEGIN
SELECT A.*
FROM (
SELECT
Data.Id,
Date,
Value,
RV,
ROW_NUMBER() OVER (ORDER BY RV) AS RN
FROM Data
inner join (SELECT Id FROM @Ids) Ids ON Ids.Id = Data.Id
WHERE RV > @Cursor
) A
WHERE RN <= @MaxRows
END
Onde @MaxRows
variará entre 500.000 e 2.000.000, dependendo de como o cliente desejar os dados.
Eu tentei abordagens diferentes:
- Indexação em (Id, RV):
CREATE NONCLUSTERED INDEX IDX_IDRV ON Data(Id, RV) INCLUDE(Date, Value);
Usando o índice, a consulta buscar as linhas onde RV = @Cursor
para cada Id
no @Ids
, leia as seguintes linhas em seguida, mesclar o resultado e classificar.
A eficiência depende então da posição relativa do @Cursor
valor.
Se estiver próximo do final dos dados (ordenado por RV), a consulta será instantânea e, se não, a consulta poderá levar alguns minutos (nunca deixe que ela seja executada até o final).
o problema dessa abordagem é que ele @Cursor
está próximo do final dos dados e a classificação não é dolorosa (nem mesmo necessária se a consulta retornar menos linhas do que @MaxRows
) ou está mais atrasada e a consulta precisa classificar as @MaxRows * LEN(@Ids)
linhas.
- Indexação no RV:
CREATE NONCLUSTERED INDEX IDX_RV ON Data(RV) INCLUDE(Id, Date, Value);
Usando o índice, a consulta procura a linha onde RV = @Cursor
, em seguida, lê todas as linhas descartando os IDs não solicitados até atingir @MaxRows
.
A eficiência depende da% de IDs solicitados ( LEN(@Ids) / COUNT(DISTINCT Id)
) e de sua distribuição.
Id% mais solicitado significa menos linhas descartadas, o que significa leituras mais eficientes, menos% Id id solicitado significa mais linhas descartadas, o que significa mais leituras para a mesma quantidade de linhas resultantes.
O problema dessa abordagem é que, se os IDs solicitados contiverem apenas alguns elementos, talvez seja necessário ler o índice inteiro para obter as linhas desejadas.
- Usando índice filtrado ou visualizações indexadas
CREATE NONCLUSTERED INDEX IDX_RVClient1 ON Data(Id, RV) INCLUDE(Date, Value)
WHERE Id IN (/* list of Ids for specific client*/);
Ou
CREATE VIEW vDataClient1 WITH SCHEMABINDING
AS
SELECT
Id,
Date,
Value,
RV
FROM dbo.Data
WHERE Id IN (/* list of Ids for specific client*/)
CREATE UNIQUE CLUSTERED INDEX IDX_IDRV ON vDataClient1(Id, Rv);
Esse método permite planos de execução de consulta e indexação perfeitamente eficientes, mas apresenta desvantagens: 1. Praticamente, terei que implementar o SQL dinâmico para criar índices ou visualizações e modificar o procedimento de solicitação para usar o índice ou visualização correta. 2. Terei que manter um índice ou visualização pelo cliente existente, incluindo armazenamento. 3. Toda vez que um cliente precisar modificar sua lista de IDs solicitados, terei que eliminar o índice ou visualizar e recriá-lo.
Não consigo encontrar um método que atenda às minhas necessidades.
Estou procurando idéias melhores para implementar a recuperação incremental de dados. Essas idéias podem implicar uma reformulação do esquema solicitante ou do esquema do banco de dados, embora eu prefira uma abordagem de indexação melhor, se houver.
fonte
Value
coluna. @ crokusek: Não pedir por RV, ID em vez de RV apenas aumenta a carga de trabalho de classificação sem nenhum benefício, não entendo o raciocínio por trás do seu comentário. Pelo que li, o RV deve ser único, a menos que insira dados especificamente nessa coluna, o que o aplicativo não faz.Respostas:
Uma solução é que o aplicativo cliente lembre-se do máximo
rowversion
por ID. O tipo de tabela definido pelo usuário mudaria para:A consulta no procedimento pode ser reescrita para usar o
APPLY
padrão (consulte meus artigos do SQLServerCentral parte 1 e parte 2 - é necessário fazer login grátis). A chave para um bom desempenho aqui é:ORDER BY
- evita a pré-busca não ordenada na junção de loops aninhados. ARECOMPILE
é necessária para permitir que o optimizador ver a cardinalidade da variável de tabela no tempo de compilação (provavelmente resultante de um plano paralelo desejável).Você deve obter um plano de consulta pós-execução como este (o plano estimado será serial):
fonte
MAX(RV)
por ID (ou um sistema de assinatura em que o aplicativo interno se lembre de todos os pares de ID / RV) e eu uso esse padrão para outro cliente. Uma outra solução era forçar o cliente a recuperar sempre todos os IDs (o que torna o problema de indexação trivial). Ele ainda não cobre a necessidade específica de pergunta: recuperação incremental de um subconjunto de IDs com apenas um contador global fornecido pelo cliente.Se possível, eu reformularia a tabela. Se pudermos ter VersionNumber como um número inteiro incremental sem intervalos, a tarefa de recuperar o próximo pedaço é uma varredura de intervalo totalmente trivial. Tudo o que precisamos é do seguinte índice:
Obviamente, precisamos garantir que o VersionNumber comece com um e não tenha lacunas. Isso é fácil de fazer com restrições.
fonte
VersionNumber
? Em ambos os casos, não consigo ver como isso ajudará na pergunta. Você poderia elaborar mais?O que eu teria feito:
Nesse caso, sua PK deve ser um campo de identidade "Chave substituta" que é incrementado automaticamente.
Como você já está na casa dos bilhões, seria melhor usar um BigInt.
Vamos chamá-lo de DataID .
Isso vai:
Defina seu novo BigInt PK ( DataID ) para usar um Índice de Cluster
:
Crie um índice não agrupado em torno de (data, ID).
Isso vai:
Crie um índice não clusterizado em (RV, ID).
Isso vai:
fonte