2009-12-09 5 views
4

J'ai une base de données très volumineuse (~ 100 Go) composée principalement de deux tables que je veux réduire (dont environ 50 millions d'enregistrements). J'ai un DB d'archives mis en place sur le même serveur avec ces deux tables, en utilisant le même schéma. J'essaie de déterminer la meilleure façon conceptuelle de supprimer les lignes de la base de données dynamique et de les insérer dans la base de données d'archives. Dans ce pseudocode est ce que je fais maintenant:Meilleure pratique pour une procédure stockée d'archivage SQL

Declare @NextIDs Table(UniqueID) 
Declare @twoYearsAgo = two years from today's date 

Insert into @NextIDs 
    SELECT top 100 from myLargeTable Where myLargeTable.actionDate < twoYearsAgo 

Insert into myArchiveTable 
<fields> 
SELECT <fields> 
FROM myLargeTable INNER JOIN @NextIDs on myLargeTable.UniqueID = @NextIDs.UniqueID 

DELETE MyLargeTable 
FROM MyLargeTable INNER JOIN @NextIDs on myLargeTable.UniqueID = @NextIDs.UniqueID 

En ce moment, cela prend 7 minutes lente épouvantablement pour terminer 1000 enregistrements. J'ai testé la suppression et l'insertion, les deux prenant environ. 3,5 minutes à compléter, donc ce n'est pas nécessairement un est radicalement plus inefficace que l'autre. Quelqu'un peut-il souligner quelques idées d'optimisation dans ce domaine?

Merci!

Ceci est SQL Server 2000.

Edit: Sur la grande table il y a un index ordonné en clusters sur le terrain de ActionDate. Il existe deux autres index, mais aucun n'est référencé dans aucune des requêtes. La table Archive n'a pas d'index. Sur mon serveur de test, c'est la seule requête qui arrive sur SQL Server, elle devrait donc avoir beaucoup de puissance de traitement.

code (ce qui fait une boucle en lots de 1000 enregistrements à la fois):

DECLARE @NextIDs TABLE(UniqueID int primary key) 
DECLARE @TwoYearsAgo datetime 
SELECT @TwoYearsAgo = DATEADD(d, (-2 * 365), GetDate()) 

WHILE EXISTS(SELECT TOP 1 UserName FROM [ISAdminDB].[dbo].[UserUnitAudit] WHERE [ActionDateTime] < @TwoYearsAgo) 
BEGIN 

BEGIN TRAN 

--get all records to be archived 
INSERT INTO @NextIDs(UniqueID) 
     SELECT TOP 1000 UniqueID FROM [ISAdminDB].[dbo].[UserUnitAudit] WHERE [UserUnitAudit].[ActionDateTime] < @TwoYearsAgo 

--insert into archive table 
INSERT INTO [ISArchive].[dbo].[userunitaudit] 
(<Fields>) 
SELECT <Fields> 
FROM [ISAdminDB].[dbo].[UserUnitAudit] AS a 
     INNER JOIN @NextIDs AS b ON a.UniqueID = b.UniqueID 

--remove from Admin DB 
DELETE [ISAdminDB].[dbo].[UserUnitAudit] 
FROM [ISAdminDB].[dbo].[UserUnitAudit] AS a 
INNER JOIN @NextIDs AS b ON a.UniqueID = b.UniqueID 

DELETE FROM @NextIDs 

COMMIT 

END 
+0

Avez-vous un index clusterisé dans le champ de clé primaire? – feihtthief

+0

NextID est une variable de table ou une table temporaire? S'il s'agit d'une variable de table, essayez plutôt une table temporaire. J'ai eu des problèmes de performances avec les variables de table dans SQL Server 2000; mais pas si mal. –

+0

NextID est actuellement déclaré en tant que variable de table. Je vais essayer d'utiliser une table temporaire. – Kevin

Répondre

1

INSERT et DELETE rejoignent sur

[ISAdminDB].[dbo].[UserUnitAudit].UniqueID 

S'il n'y a pas d'index sur ce point, et vous indique qu'il n'y a pas, vous faites deux tables balayages. C'est probablement la source de la lenteur, b/c une analyse de table SQL Server lit la table entière dans une table de travail, recherche la table de travail pour les lignes correspondantes, puis laisse tomber la table de travail.

Je pense que vous devez ajouter un index sur UniqueID. Le succès des performances pour le maintenir doit être inférieur à celui des analyses de table. Et vous pouvez le laisser tomber une fois votre archive terminée.

+0

Cela m'a conduit à la solution. Au lieu de garder une trace des lignes que je devais déplacer par UniqueID qui n'a pas d'index, j'ai simplement utilisé la clause WHERE [ActionDateTime] <@TwoYearsAgo sur mon insert et supprimer, et l'alto, beaucoup plus rapidement. – Kevin

1

Y at-il des indices sur myLargeTable.actionDate et .UniqueID?

+0

Il y a un index clusered sur l'actionDate, mais rien sur l'uniqueID. Il n'y a pas d'index sur la table d'archivage dans laquelle elle est insérée. – Kevin

+0

Vous avez besoin d'un index sur myLargeTable.UniqueId pour les JOINs. Vérifiez le plan d'exécution dans l'Analyseur de requêtes et vous verrez probablement des analyses de table. –

+0

Merci Jonas, je vais aller parler au gars qui a conçu la base de données pour voir pourquoi nous n'avons pas d'index sur le champ uniqueID. On dirait que cela aurait du sens ... – Kevin

1

Avez-vous essayé des tailles de lots plus grandes que 100?

Qu'est-ce qui prend le plus de temps? L'INSERT, ou le DELETE?

+0

Lorsque je lance la taille d'un lot jusqu'à 1 000, l'insertion et la suppression prennent environ 3 minutes et demie pour être exécutées séparément. L'insertion initiale dans NextIDs prend seulement une seconde. – Kevin

4

Vous avez effectivement trois sélections qui doivent être exécutés avant votre insertion/suppression des commandes sont exécutées:

pour le 1er insert:

SELECT top 100 from myLargeTable Where myLargeTable.actionDate < twoYearsAgo 

pour la 2ème insert:

SELECT <fields> FROM myLargeTable INNER JOIN NextIDs 
on myLargeTable.UniqueID = NextIDs.UniqueID 

pour la suppression:

(select *) 
FROM MyLargeTable INNER JOIN NextIDs on myLargeTable.UniqueID = NextIDs.UniqueID 

Je vais essayer de les optimiser et si elles sont toutes rapides, alors les index peuvent ralentir vos écritures. Quelques suggestions:

  1. début profileur et voir ce happenng la lecture/écriture, etc.

  2. utilisation d'un index de contrôle pour les trois déclarations.

  3. essayez d'exécuter le SELECTs retour seulement le PK, pour voir si le retard est l'exécution de la requête ou de récupérer les données (n'ont par exemple tous les champs indexés en texte intégral, TEXT champs etc.)

4

Effectuer vous avoir un index sur la table source pour la colonne que vous utilisez pour filtrer les résultats? Dans ce cas, ce serait l'actionDate.

En outre, il peut souvent être utile de supprimer tous les index de la table de destination avant d'effectuer des insertions massives, mais dans ce cas, vous n'en faites que 100 à la fois.

Vous feriez aussi probablement mieux de le faire en plus gros lots. Avec cent à la fois les frais généraux des requêtes vont finir par dominer les coûts/temps.

Y a-t-il une autre activité sur le serveur pendant cette période? Y a-t-il un blocage?

Espérons que cela vous donne un point de départ.

Si vous pouvez fournir le code exact que vous utilisez (peut-être sans les noms de colonnes s'il y a des problèmes de confidentialité) alors peut-être que quelqu'un peut trouver d'autres façons d'optimiser.

EDIT: Avez-vous vérifié le plan de requête pour votre bloc de code? J'ai rencontré des problèmes avec des variables de table comme celle-ci, où l'optimiseur de requêtes ne pouvait pas comprendre que la variable table serait de petite taille, donc elle essayait toujours de faire un scan complet de la table de base. Dans mon cas, il est finalement devenu un point discutable, donc je ne suis pas sûr de la solution ultime. Vous pouvez certainement ajouter une condition sur l'actionDate à toutes vos requêtes de sélection, ce qui en minimiserait au moins les effets.

L'autre option consisterait à utiliser une table normale pour contenir les ID.

+0

Voir ma modification dans l'OP. Je pense que cela répond à toutes vos questions. – Kevin

0

Vous pouvez essayer de le faire en utilisant la clause de sortie:

declare @items table (
    <field list just like source table>) 

delete top 100 source_table 
    output deleted.first_field, deleted.second_field, etc 
    into @items 
    where <conditions> 

insert archive_table (<fields>) 
    select (<fields>) from @items 

Vous pourriez également être en mesure de le faire en une seule requête, en faisant « sortie dans » directement dans la table d'archivage (éliminant le besoin de la table var)

Questions connexes