web-dev-qa-db-fra.com

Le moyen le plus rapide de mettre à jour 120 millions d'enregistrements

J'ai besoin d'initialiser un nouveau champ avec la valeur -1 dans une table d'enregistrement de 120 millions.

Update table
       set int_field = -1;

Je l'ai laissé fonctionner pendant 5 heures avant de l'annuler.

J'ai essayé de l'exécuter avec le niveau de transaction défini pour lire sans engagement avec les mêmes résultats.

Recovery Model = Simple.
MS SQL Server 2005

Avez-vous des conseils pour que cela se fasse plus rapidement?

44
Bob Probst

La seule façon saine de mettre à jour une table de 120 millions d'enregistrements est d'utiliser une instruction SELECT qui remplit une table second. Vous devez faire attention lorsque vous faites cela. Instructions ci-dessous.


cas simple

Pour une table sans index clusterisé, pendant un temps sans DML simultané:

  • SELECT *, new_col = 1 INTO clone.BaseTable FROM dbo.BaseTable
  • recréer des index, des contraintes, etc. sur une nouvelle table
  • passer de l'ancien au nouveau avec ALTER SCHEMA ... TRANSFER.
  • déposer l'ancienne table

Si vous ne pouvez pas créer un schéma de clonage, un nom de table différent dans le même schéma fera l'affaire. N'oubliez pas de renommer toutes vos contraintes et déclencheurs (le cas échéant) après le changement.


Cas non simple

Tout d'abord, recréez votre BaseTable avec le même nom sous un schéma différent, par exemple clone.BaseTable. L'utilisation d'un schéma distinct simplifiera le processus de renommage ultérieurement.

  • Inclure l'index cluster, le cas échéant. N'oubliez pas que les clés primaires et les contraintes uniques peuvent être regroupées, mais pas nécessairement.
  • Inclure les colonnes d'identité et les colonnes calculées, le cas échéant.
  • Incluez votre nouvelle colonne INT, où qu'elle appartienne.
  • Ne pas inclure l'un des éléments suivants:
    • déclencheurs
    • contraintes de clé étrangère
    • index non-cluster/clés primaires/contraintes uniques
    • vérifier les contraintes ou les contraintes par défaut. Les valeurs par défaut ne font pas beaucoup de différence, mais nous essayons de garder les choses minimales.

Ensuite, testez votre insert avec 1000 lignes:

-- assuming an IDENTITY column in BaseTable
SET IDENTITY_INSERT clone.BaseTable ON
GO
INSERT clone.BaseTable WITH (TABLOCK) (Col1, Col2, Col3)
SELECT TOP 1000 Col1, Col2, Col3 = -1
FROM dbo.BaseTable
GO
SET IDENTITY_INSERT clone.BaseTable OFF

Examinez les résultats. Si tout apparaît dans l'ordre:

  • tronquer la table de clonage
  • assurez-vous que la base de données est dans un modèle de récupération enregistré en bloc ou simple
  • effectuer l'insertion complète.

Cela prendra un certain temps, mais pas autant qu'une mise à jour. Une fois terminé, vérifiez les données dans la table de clonage pour vous assurer que tout est correct.

Ensuite, recréez toutes les clés primaires non clusterisées/contraintes/index uniques et contraintes de clé étrangère (dans cet ordre). Recréez les contraintes par défaut et vérifiez les contraintes, le cas échéant. Recréez tous les déclencheurs. Recréez chaque contrainte, index ou déclencheur dans un lot distinct. par exemple:

ALTER TABLE clone.BaseTable ADD CONSTRAINT UQ_BaseTable UNIQUE (Col2)
GO
-- next constraint/index/trigger definition here

Enfin, déplacez dbo.BaseTable vers un schéma de sauvegarde et clone.BaseTable au schéma dbo (ou là où votre table est censée vivre).

-- -- perform first true-up operation here, if necessary
-- EXEC clone.BaseTable_TrueUp
-- GO
-- -- create a backup schema, if necessary
-- CREATE SCHEMA backup_20100914
-- GO
BEGIN TRY
  BEGIN TRANSACTION
  ALTER SCHEMA backup_20100914 TRANSFER dbo.BaseTable
  -- -- perform second true-up operation here, if necessary
  -- EXEC clone.BaseTable_TrueUp
  ALTER SCHEMA dbo TRANSFER clone.BaseTable
  COMMIT TRANSACTION
END TRY
BEGIN CATCH
  SELECT ERROR_MESSAGE() -- add more info here if necessary
  ROLLBACK TRANSACTION
END CATCH
GO

Si vous avez besoin de libérer de l'espace disque, vous pouvez supprimer votre table d'origine à ce stade, mais il peut être prudent de la conserver plus longtemps.

Inutile de dire que c'est idéalement une opération hors ligne. Si vous avez des personnes qui modifient des données pendant que vous effectuez cette opération, vous devrez effectuer une opération true-up avec le commutateur de schéma. Je recommande de créer un déclencheur sur dbo.BaseTable pour consigner tous les fichiers DML dans une table distincte. Activez ce déclencheur avant de démarrer l'insertion. Ensuite, dans la même transaction que vous effectuez le transfert de schéma, utilisez la table des journaux pour effectuer une vérification. Testez-le d'abord sur un sous-ensemble de données! Les deltas sont faciles à visser.

36
Peter Radocchia

Si vous disposez de l'espace disque, vous pouvez utiliser SELECT INTO et créer une nouvelle table. Il est peu connecté, donc ça irait beaucoup plus vite

select t.*, int_field = CAST(-1 as int)
into mytable_new 
from mytable t

-- create your indexes and constraints

GO

exec sp_rename mytable, mytable_old
exec sp_rename mytable_new, mytable

drop table mytable_old
13
Mike Forman

Je divise la tâche en unités plus petites. Testez avec différents intervalles de taille de lot pour votre table, jusqu'à ce que vous trouviez un intervalle qui fonctionne de manière optimale. Voici un échantillon que j'ai utilisé dans le passé.

declare @counter int 
declare @numOfRecords int
declare @batchsize int

set @numOfRecords = (SELECT COUNT(*) AS NumberOfRecords FROM <TABLE> with(nolock))
set @counter = 0 
set @batchsize = 2500

set rowcount @batchsize
while @counter < (@numOfRecords/@batchsize) +1
begin 
set @counter = @counter + 1 
Update table set int_field = -1 where int_field <> -1;
end 
set rowcount 0
9
KC.

Si votre int_field est indexé, supprimez l'index avant d'exécuter la mise à jour. Ensuite, créez à nouveau votre index ...

5 heures semblent beaucoup pour 120 millions d'enregistrements.

4
Pablo Santa Cruz
set rowcount 1000000
Update table set int_field = -1 where int_field<>-1

voir à quelle vitesse cela prend, ajustez et répétez si nécessaire

3
BlackTigerX

Ce que j'essaierais d'abord c'est
pour supprimer toutes les contraintes, index, déclencheurs et index de texte intégral avant de mettre à jour.

Si ci-dessus n'était pas assez performant, mon prochain coup serait
pour créer un fichier CSV avec 12 millions d'enregistrements et l'importer en masse à l'aide de bcp.

Enfin, je créerais une nouvelle table de tas (signifiant table sans clé primaire) sans index sur un groupe de fichiers différent, remplissez-la avec -1. Partitionnez l'ancienne table et ajoutez la nouvelle partition en utilisant "switch".

2
Sung M. Kim

Lorsque en ajoutant une nouvelle colonne ("initialiser un nouveau champ") et en définissant une seule valeur pour chaque ligne existante, j'utilise la tactique suivante:

ALTER TABLE MyTable
 add NewColumn  int  not null
  constraint MyTable_TemporaryDefault
   default -1

ALTER TABLE MyTable
 drop constraint MyTable_TemporaryDefault

Si la colonne est nullable et que vous n'incluez pas de contrainte "déclarée", la colonne sera définie sur null pour toutes les lignes.

2
Philip Kelley
declare @cnt bigint
set @cnt = 1

while @cnt*100<10000000 
 begin

UPDATE top(100) [Imp].[dbo].[tablename]
   SET [col1] = xxxx       
 WHERE[col2] is null  

  print '@cnt: '+convert(varchar,@cnt)
  set @cnt=@cnt+1
  end
2
aads

Cela ressemble à un problème d'indexation, comme l'a mentionné Pabla Santa Cruz. Étant donné que votre mise à jour n'est pas conditionnelle, vous pouvez supprimer la colonne et la ré-ajouter avec une valeur par défaut.

1
Brad

Si la table a un index sur lequel vous pouvez itérer, je mettrais l'instruction update top(10000) dans une boucle while se déplaçant sur les données. Cela garderait le journal des transactions mince et n'aura pas un impact aussi énorme sur le système de disque. Aussi, je recommanderais de jouer avec l'option maxdop (en la réglant plus près de 1).

0
Denis Valeev

En général, les recommandations sont les suivantes:

  1. Supprimer ou simplement désactiver tous les INDEX, DÉCLENCHEURS, CONTRAINTES sur la table;
  2. Effectuer COMMIT plus souvent (par exemple, après chaque 1000 enregistrements mis à jour);
  3. Utilisez select ... into.

Mais dans un cas particulier, vous devez choisir la solution la plus appropriée ou leur combinaison.

Gardez également à l'esprit qu'un index peut parfois être utile, par exemple lorsque vous effectuez la mise à jour de la colonne non indexée par une condition.

0
alexber