SQL Server 2005
Eu preciso ser capaz de processar continuamente cerca de 350 milhões de registros em uma tabela de 900 milhões. A consulta que estou usando para selecionar os registros a serem processados se torna muito fragmentada à medida que eu processo e preciso interromper o processamento para reconstruir o índice. Modelo e consulta de pseudo dados ...
/**************************************/
CREATE TABLE [Table]
(
[PrimaryKeyId] [INT] IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
[ForeignKeyId] [INT] NOT NULL,
/* more columns ... */
[DataType] [CHAR](1) NOT NULL,
[DataStatus] [DATETIME] NULL,
[ProcessDate] [DATETIME] NOT NULL,
[ProcessThreadId] VARCHAR (100) NULL
);
CREATE NONCLUSTERED INDEX [Idx] ON [Table]
(
[DataType],
[DataStatus],
[ProcessDate],
[ProcessThreadId]
);
/**************************************/
/**************************************/
WITH cte AS (
SELECT TOP (@BatchSize) [PrimaryKeyId], [ProcessThreadId]
FROM [Table] WITH ( ROWLOCK, UPDLOCK, READPAST )
WHERE [DataType] = 'X'
AND [DataStatus] IS NULL
AND [ProcessDate] < DATEADD(m, -2, GETDATE()) -- older than 2 months
AND [ProcessThreadId] IS NULL
)
UPDATE cte
SET [ProcessThreadId] = @ProcessThreadId;
SELECT * FROM [Table] WITH ( NOLOCK )
WHERE [ProcessThreadId] = @ProcessThreadId;
/**************************************/
Conteúdo dos dados ...
Enquanto a coluna [DataType] é digitada como CHAR (1), cerca de 35% de todos os registros são iguais a 'X' e o restante é igual a 'A'.
Apenas dos registros em que [DataType] é igual a 'X', cerca de 10% terão um valor NOT NULL [DataStatus].
As colunas [ProcessDate] e [ProcessThreadId] serão atualizadas para cada registro processado.
A coluna [DataType] é atualizada ('X' é alterado para 'A') cerca de 10% do tempo.
A coluna [DataStatus] é atualizada menos de 1% do tempo.
Por enquanto, minha solução é selecionar a chave primária de todos os registros para processar em uma tabela de processamento separada. Excluo as chaves ao processá-las para que, como fragmentos do índice, eu esteja lidando com menos registros.
No entanto, isso não se encaixa no fluxo de trabalho que desejo ter, para que esses dados sejam processados continuamente, sem intervenção manual e tempo de inatividade significativo. Antecipo o tempo de inatividade trimestralmente para as tarefas domésticas. Mas agora, sem a tabela de processamento separada, não consigo processar nem metade do conjunto de dados sem que a fragmentação se torne tão ruim que exija a interrupção e a reconstrução do índice.
Alguma recomendação para indexação ou um modelo de dados diferente? Existe um padrão que eu preciso pesquisar?
Eu tenho controle total do modelo de dados e do software de processo, para que nada saia da mesa.
fonte
Respostas:
O que você está fazendo é usar uma tabela como uma fila. Sua atualização é o método de remoção da fila. Mas o índice clusterizado na tabela é uma má escolha para uma fila. Usar tabelas como filas, na verdade, impõe requisitos bastante rigorosos ao design da tabela. Seu índice de cluster deve estar na ordem de desenfileiramento, provavelmente nesse caso
([DataType], [DataStatus], [ProcessDate])
. Você pode implementar a chave primária como uma restrição não clusterizada . Solte o índice não agrupadoIdx
, pois a chave agrupada assume sua função.Outra peça importante do quebra-cabeça é manter o tamanho da linha constante durante o processamento. Você declarou
ProcessThreadId
como oVARCHAR(100)
que implica que a linha cresce e diminui conforme está sendo 'processada' porque o valor do campo muda de NULL para não nulo. Esse padrão de aumento e redução na linha causa divisões e fragmentação da página. Não posso imaginar um ID de segmento que seja 'VARCHAR (100)'. Use um tipo de comprimento fixo, talvez umINT
.Como observação lateral, você não precisa desenfileirar em duas etapas (UPDATE seguido de SELECT). Você pode usar a cláusula OUTPUT, conforme explicado no artigo vinculado acima:
Além disso, eu consideraria mover itens processados com êxito para uma tabela diferente de arquivamento. Você deseja que suas tabelas de filas fiquem próximas ao tamanho zero, não deseja que elas cresçam, pois elas retêm o 'histórico' de entradas antigas desnecessárias. Você também pode considerar o particionamento
[ProcessDate]
como uma alternativa ( por exemplo, uma partição ativa atual que atua como a fila e armazena entradas com NULL ProcessDate e outra partição para tudo que não seja nulo. exclui (alterna) os dados que passaram pelo período de retenção obrigatório.Se as coisas esquentarem, você poderá particionar[DataType]
se tiver seletividade suficiente, mas esse design seria realmente complicado, pois exige particionamento por coluna computada persistente (uma coluna composta que cola [DataType] e [ProcessingDate]).fonte
Eu começaria movendo os campos
ProcessDate
eProcessthreadid
para outra tabela.No momento, todas as linhas selecionadas nesse índice bastante amplo também precisam ser atualizadas.
Se você mover esses dois campos para outra tabela, seu volume de atualização na tabela principal será reduzido em 90%, o que deve cuidar da maior parte da fragmentação.
Você ainda terá fragmentação na tabela NEW, mas será mais fácil gerenciar em uma tabela mais estreita com muito menos dados.
fonte