SQL Server 2005
J'ai besoin de pouvoir traiter en continu environ 350 millions d'enregistrements dans une table d'enregistrement de 900 millions. La requête que j'utilise pour sélectionner les enregistrements à traiter devient très fragmentée au fur et à mesure que je traite et j'ai besoin d'arrêter le traitement pour reconstruire l'index. Pseudo modèle de données et requête ...
/**************************************/
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;
/**************************************/
Contenu des données ...
Alors que la colonne [DataType] est tapée en tant que CHAR (1), environ 35% de tous les enregistrements sont égaux à «X», le reste égal à «A».
Seuls les enregistrements où [DataType] est égal à «X», environ 10% auront une valeur NOT NULL [DataStatus].
Les colonnes [ProcessDate] et [ProcessThreadId] seront mises à jour pour chaque enregistrement traité.
La colonne [DataType] est mise à jour («X» est remplacé par «A») environ 10% du temps.
La colonne [DataStatus] est mise à jour moins de 1% du temps.
Pour l'instant, ma solution consiste à sélectionner la clé primaire de tous les enregistrements à traiter dans une table de traitement distincte. Je supprime les clés au fur et à mesure que je les traite afin qu'en tant que fragments d'index, je traite moins d'enregistrements.
Cependant, cela ne correspond pas au flux de travail que je souhaite avoir afin que ces données soient traitées en continu, sans intervention manuelle et temps d'arrêt important. Je prévois des temps d'arrêt tous les trimestres pour les tâches ménagères. Mais maintenant, sans la table de traitement séparée, je ne peux pas passer à travers le traitement de la moitié de l'ensemble de données sans que la fragmentation ne devienne si mauvaise qu'elle nécessite l'arrêt et la reconstruction de l'index.
Des recommandations pour l'indexation ou un modèle de données différent? Existe-t-il un modèle que je dois rechercher?
J'ai un contrôle total sur le modèle de données et le logiciel de processus, donc rien n'est hors de table.
la source
Réponses:
Vous utilisez une table comme file d'attente. Votre mise à jour est la méthode de retrait. Mais l'index cluster sur la table est un mauvais choix pour une file d'attente. L'utilisation de tables comme files d'attente impose en fait des exigences assez strictes sur la conception des tables. Votre index cluster doit être l'ordre de retrait, dans ce cas probable
([DataType], [DataStatus], [ProcessDate])
. Vous pouvez implémenter la clé primaire en tant que contrainte non clusterisée . Supprimez l'index non clusteriséIdx
, car la clé clusterisée joue son rôle.Une autre pièce importante du puzzle consiste à maintenir la taille de la ligne constante pendant le traitement. Vous avez déclaré le en
ProcessThreadId
tant que,VARCHAR(100)
ce qui implique que la ligne grandit et se rétrécit au fur et à mesure qu'elle est «traitée», car la valeur du champ passe de NULL à non nulle. Ce modèle d'agrandissement et de réduction sur la ligne provoque des fractionnements de page et une fragmentation. Je ne peux pas imaginer un ID de thread qui soit 'VARCHAR (100)'. Utilisez un type de longueur fixe, peut-être unINT
.En guise de remarque, vous n'avez pas besoin de retirer la file d'attente en deux étapes (UPDATE suivi de SELECT). Vous pouvez utiliser la clause OUTPUT, comme expliqué dans l'article lié ci-dessus:
De plus, j'envisagerais de déplacer des éléments traités avec succès dans une autre table d'archive. Vous voulez que vos tables de files d'attente planent près de la taille zéro, vous ne voulez pas qu'elles grandissent car elles conservent «l'historique» des anciennes entrées inutiles. Vous pouvez également envisager le partitionnement par
[ProcessDate]
comme alternative (c'est-à-dire une partition active actuelle qui fait office de file d'attente et stocke les entrées avec NULL ProcessDate, et une autre partition pour tout ce qui n'est pas nul. Ou plusieurs partitions pour non-nul si vous voulez implémenter un système efficace supprime (basculer) pour les données qui ont passé la période de rétention obligatoire. Si les choses deviennent chaudes, vous pouvez partitionner en plus en[DataType]
s'il a suffisamment de sélectivité, mais cette conception serait vraiment compliquée car elle nécessite un partitionnement par colonne calculée persistante (une colonne composite qui colle ensemble [DataType] et [ProcessingDate]).la source
Je commencerais par déplacer les champs
ProcessDate
etProcessthreadid
vers une autre table.À l'heure actuelle, chaque ligne que vous sélectionnez dans cet index assez large doit également être mise à jour.
Si vous déplacez ces deux champs vers une autre table, votre volume de mise à jour sur la table principale est réduit de 90%, ce qui devrait prendre en charge la majeure partie de la fragmentation.
Vous aurez toujours une fragmentation dans la nouvelle table, mais ce sera plus facile à gérer sur une table plus étroite avec beaucoup moins de données.
la source