J'ai une table avec 64 millions de lignes prenant 4,3 Go sur le disque pour ses données.
Chaque ligne contient environ 30 octets de colonnes entières, plus une NVARCHAR(255)
colonne variable pour le texte.
J'ai ajouté une colonne NULLABLE avec type de données Datetimeoffset(0)
.
J'ai ensuite mis à jour cette colonne pour chaque ligne et je me suis assuré que toutes les nouvelles insertions placent une valeur dans cette colonne.
Une fois qu'il n'y avait plus d'entrées NULL, j'ai ensuite exécuté cette commande pour rendre mon nouveau champ obligatoire:
ALTER TABLE tblCheckResult
ALTER COLUMN [dtoDateTime] [datetimeoffset](0) NOT NULL
Le résultat a été une énorme croissance de la taille du journal des transactions - de 6 Go à plus de 36 Go jusqu'à épuisement de l'espace!
Quelqu'un a-t-il une idée de ce que fait SQL Server 2008 R2 pour que cette commande simple génère une telle croissance?
NOT NULL
colonne avec une valeur par défaut en tant qu'opération de métadonnées. Voir également "Ajout de colonnes NOT NULL en tant qu'opération en ligne" dans la documentation .Réponses:
Lorsque vous modifiez une colonne en NOT NULL, SQL Server doit toucher chaque page, même s'il n'y a pas de valeur NULL. En fonction de votre facteur de remplissage, cela pourrait entraîner beaucoup de fractionnements de pages. Bien sûr, chaque page touchée doit être consignée et je soupçonne, à cause des scissions, que deux modifications doivent être consignées pour plusieurs pages. Comme tout est fait en un seul passage, cependant, le journal doit prendre en compte tous les changements. Par conséquent, si vous appuyez sur Cancel, il sait exactement ce qu'il faut annuler.
Un exemple. Tableau simple:
Maintenant, regardons les détails de la page. Nous devons d’abord savoir à quelle page et à DB_ID nous avons affaire. Dans mon cas, j'ai créé une base de données appelée
foo
, et le DB_ID s'est avéré être 5.La sortie a indiqué que la page 159 m'intéressait (la seule ligne en
DBCC IND
sortie avecPageType = 1
).Maintenant, regardons quelques détails de pages sélectionnées au fur et à mesure que nous explorons le scénario du PO.
Maintenant, je n'ai pas toutes les réponses à cette question, car je ne suis pas un type interne profond. Mais il est clair que, même si l'opération de mise à jour et l'ajout de la contrainte NOT NULL écrivent indéniablement sur la page, cette dernière le fait d'une manière totalement différente. Cela semble en fait changer la structure de l'enregistrement, plutôt que de simplement manipuler des bits, en permutant la colonne nullable par une colonne non nullable. Je ne sais pas trop pourquoi , mais je suppose que c'est une bonne question pour l'équipe du moteur de stockage . Je pense que SQL Server 2012 gère beaucoup mieux certains de ces scénarios, FWIW - mais je n'ai pas encore procédé à des tests exhaustifs.
la source
Lors de l'exécution de la commande
Cela semble être implémenté comme une opération d'ajout de colonne, de mise à jour ou de suppression de colonne.
sys.sysrscols
pour représenter une nouvelle colonne. Lestatus
bit pour128
se mettre en indiquant la colonne ne permet pas deNULL
ssys.sysrscols
.rscolid
Mis à jour en un grand entier et lestatus
bit 2 activé).sys.sysrscols
pour la nouvelle colonne est modifiée pour lui donner lerscolid
de l'ancienne colonne.L'opération susceptible de générer beaucoup de journalisation est la
UPDATE
de toutes les lignes de la table, mais cela ne signifie pas que cela se produira toujours . Si les images "avant" et "après" de la ligne sont identiques, cela sera traité comme une mise à jour sans mise à jour et ne sera pas enregistré depuis mes tests.L'explication de la raison pour laquelle vous obtenez beaucoup de journalisation dépendra donc du fait que les versions "avant" et "après" de la ligne ne sont pas exactement les mêmes.
Pour les colonnes de longueur variable stockées au
FixedVar
format, j'ai constaté que ce paramètreNOT NULL
entraînait toujours une modification de la ligne à consigner. Le nombre de colonnes et le nombre de colonnes de longueur variable sont tous deux incrémentés et la nouvelle colonne est ajoutée à la fin de la section de longueur variable dupliquant les données.datetimeoffset(0)
Toutefois, sa longueur est fixe et pour les colonnes de longueur fixe stockées auFixedVar
format, les anciennes et les nouvelles colonnes semblent se voir attribuer le même créneau dans la partie de données de longueur fixe de la ligne et, comme elles ont la même longueur et la même valeur, les "avant" et les versions "après" de la ligne sont les mêmes . Ceci peut être vu dans la réponse de @ Aaron. Les deux versions de la ligne avant et après leALTER TABLE dbo.floob ALTER COLUMN bar INT NOT NULL;
sontCeci n'est pas connecté.
Logiquement, d'après ma description des événements, la ligne devrait en fait être différente ici car le nombre de colonnes
02
devrait être augmenté,03
mais aucun changement de ce type ne se produit réellement dans la pratique.Certaines raisons possibles expliquant pourquoi cela peut se produire dans une colonne de longueur fixe sont les suivantes:
SPARSE
la nouvelle colonne serait stockée dans une autre partie de la ligne que la colonne d'origine, ce qui aurait pour effet de rendre différentes les images de lignes avant et après.ALTER TABLE
opération antérieure mise en œuvre sous la forme d'une modification de métadonnées uniquement et n'ayant pas encore été appliquée à la ligne. Par exemple, si une nouvelle colonne de longueur variable nullable a été ajoutée, elle est appliquée à l'origine en tant que métadonnées uniquement. Elle est en fait uniquement écrite dans les lignes lors de leur prochaine mise à jour (l'écriture qui se produit réellement dans cette dernière instance n'est que des mises à jour). la section de comptage de colonne et l'NULL_BITMAP
uneNULL
varchar
colonne à l'extrémité de la rangée ne prend pas de place)la source
J'ai rencontré le même problème concernant une table ayant 200.000.000 lignes. Initialement, j'ai ajouté la colonne nullable, puis mis à jour toutes les lignes et enfin modifié la colonne en
NOT NULL
via uneALTER TABLE ALTER COLUMN
instruction. Cela a entraîné deux transactions énormes faisant exploser le fichier journal incroyablement (170 Go de croissance).Le moyen le plus rapide que j'ai trouvé était le suivant:
Ajouter la colonne en utilisant une valeur par défaut
Supprimez la contrainte par défaut à l'aide de SQL dynamique, car la contrainte n'a pas encore été nommée:
Le temps d'exécution est passé de> 30 minutes à 10 minutes, y compris la réplication des modifications via la réplication transactionnelle. J'exécute une installation de SQL Server 2008 (SP2).
la source
J'ai couru le test suivant:
Je pense que cela a à voir avec l'espace réservé que le journal conserve, au cas où vous annuleriez la transaction. Recherchez dans la fonction fn_dblog la colonne "Log Reserve" pour la ligne LOP_BEGIN_XACT et voyez combien d'espace elle tente de réserver.
la source
select * FROM fn_dblog(null, null) where AllocUnitName='dbo.tblCheckResult' AND Operation = 'LOP_MODIFY_ROW'
vous pouvez voir les mises à jour 10000 lignes.Le comportement de cette opération est différent dans SQL Server 2012. Voir http://rusanu.com/2011/07/13/online-non-null-with-values-column-add-in-sql-server-11/
Le nombre d'enregistrements de journal générés pour les versions de SQL Server 2008 R2 et inférieures sera considérablement plus élevé que le nombre d'enregistrements de journal pour SQL Server 2012.
la source
NOT NULL
entraîne la journalisation. La modification de 2012 concerne l'ajout d'une nouvelleNOT NULL
colonne avec une valeur par défaut.