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?
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
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.
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:
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.
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
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
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.
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
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".
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.
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
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.
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).
En général, les recommandations sont les suivantes:
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.