J'ai une table avec 64m de lignes prenant 4,3 Go sur le disque pour ses données.
Chaque ligne représente environ 30 octets de colonnes entières, plus une variable NVARCHAR(255)
colonne pour le texte.
J'ai ajouté une colonne NULLABLE avec le 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 aucune entrée NULL, j'ai alors 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'à ce qu'il manque d'espace!
Quelqu'un a-t-il une idée de ce que fait SQL Server 2008 R2 pour que cette simple commande entraîne une croissance énorme?
Lorsque vous modifiez une colonne en NOT NULL, SQL Server doit toucher chaque page unique, même s'il n'y a pas de valeurs NULL. En fonction de votre facteur de remplissage, cela pourrait entraîner de nombreux fractionnements de page. Chaque page qui est touchée, bien sûr, doit être enregistrée, et je soupçonne en raison des divisions que deux modifications peuvent devoir être enregistrées pour de nombreuses pages. Comme tout est fait en un seul passage, le journal doit tenir compte de toutes les modifications afin que, si vous appuyez sur Annuler, il sache exactement quoi annuler.
Un exemple. Table simple:
DROP TABLE dbo.floob;
GO
CREATE TABLE dbo.floob
(
id INT IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
bar INT NULL
);
INSERT dbo.floob(bar) SELECT NULL UNION ALL SELECT 4 UNION ALL SELECT NULL;
ALTER TABLE dbo.floob ADD CONSTRAINT df DEFAULT(0) FOR bar
Maintenant, regardons les détails de la page. Nous devons d'abord savoir avec 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 se trouvait être 5.
DBCC TRACEON(3604, -1);
DBCC IND('foo', 'dbo.floob', 1);
SELECT DB_ID();
La sortie a indiqué que j'étais intéressé par la page 159 (la seule ligne dans DBCC IND
sortie avec PageType = 1
).
Maintenant, regardons certains détails de la page que nous parcourons le scénario de l'OP.
DBCC PAGE(5, 1, 159, 3);
UPDATE dbo.floob SET bar = 0 WHERE bar IS NULL;
DBCC PAGE(5, 1, 159, 3);
ALTER TABLE dbo.floob ALTER COLUMN bar INT NOT NULL;
DBCC PAGE(5, 1, 159, 3);
Maintenant, je n'ai pas toutes les réponses à cela, car je ne suis pas un gars profondément interne. Mais il est clair que - alors que 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 entièrement différente. Il semble en fait changer la structure de l'enregistrement, plutôt que de simplement jouer avec des bits, en remplaçant la colonne nullable par une colonne non nullable. Pourquoi cela doit-il le faire, je ne suis pas sûr - une bonne question pour l'équipe du moteur de stockage , je suppose. Je crois que SQL Server 2012 gère beaucoup mieux certains de ces scénarios, FWIW - mais je n'ai pas encore fait de test exhaustif.
Lors de l'exécution de la commande
ALTER COLUMN ... NOT NULL
Cela semble être implémenté comme une opération d'ajout de colonne, de mise à jour et de suppression de colonne.
sys.sysrscols
Pour représenter une nouvelle colonne. Le bit status
pour 128
Est défini, indiquant que la colonne n'autorise pas NULL
ssys.sysrscols
. rscolid
mis à jour en un grand entier et status
bit 2 réglé sur indiqué supprimé)sys.sysrscols
Pour la nouvelle colonne est modifiée pour lui donner le rscolid
de l'ancienne colonne.L'opération qui a le potentiel pour provoquer beaucoup de journalisation est le UPDATE
de toutes les lignes du tableau mais cela ne signifie pas que cela va se produisent toujours . Si les images "avant" et "après" de la ligne sont identiques, cela sera traité comme un mise à jour non mise à jour et ne sera pas enregistré à partir de mes tests jusqu'à présent.
Ainsi, l'explication de la raison pour laquelle vous obtenez beaucoup de journalisation dépendra de la raison pour laquelle les versions "avant" et "après" de la ligne ne sont pas identiques.
Pour les colonnes de longueur variable stockées au format FixedVar
, j'ai constaté que la définition de NOT NULL
Entraîne toujours un changement dans la ligne qui doit être enregistrée. 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 qui duplique les données.
datetimeoffset(0)
est de longueur fixe cependant et pour les colonnes de longueur fixe stockées au format FixedVar
, les anciennes et les nouvelles colonnes semblent toutes deux avoir le même emplacement dans la partie de données de longueur fixe de la ligne et comme ils ont tous deux la même longueur et la même valeur les versions "avant" et "après" de la ligne sont les mêmes . Cela peut être vu dans la réponse de @ Aaron. Les deux versions de la ligne avant et après le ALTER TABLE dbo.floob ALTER COLUMN bar INT NOT NULL;
Sont
0x10000c00 01000000 00000000 020000
Ce n'est pas enregistré.
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 genre ne se produit réellement dans la pratique.
Certaines raisons possibles expliquant pourquoi cela peut se produire dans une colonne de longueur fixe sont
SPARSE
, la nouvelle colonne sera stockée dans une partie différente de la ligne par rapport à l'original, ce qui entraînera la différence entre les images de la ligne avant et après.ALTER TABLE
Précédente ait été implémentée en tant que modification de métadonnées uniquement et n'a pas encore été appliquée à la ligne. Par exemple, si une nouvelle colonne de longueur variable nullable a été ajoutée, elle est initialement appliquée en tant que modification des métadonnées uniquement et elle n'est réellement écrite dans les lignes que lors de leur prochaine mise à jour (l'écriture qui se produit réellement dans cette dernière instance est simplement une mise à jour de la section de comptage de colonnes et le NULL_BITMAP
en tant que colonne NULL
varchar
à la fin de la ligne ne prend pas de place)J'ai rencontré le même problème concernant une table ayant 200 000 000 lignes. Au départ, j'ai ajouté la colonne nullable, puis mis à jour toutes les lignes et finalement modifié la colonne en NOT NULL
via un ALTER TABLE ALTER COLUMN
déclaration. Cela a entraîné deux énormes transactions faisant exploser le fichier journal de manière incroyable (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
ALTER TABLE table1 ADD column1 INT NOT NULL DEFAULT (1)
Supprimez la contrainte par défaut à l'aide de SQL dynamique car la contrainte n'a pas été nommée auparavant:
DECLARE
@constraint_name SYSNAME,
@stmt NVARCHAR(510);
SELECT @CONSTRAINT_NAME = DC.NAME
FROM SYS.DEFAULT_CONSTRAINTS DC
INNER JOIN SYS.COLUMNS C
ON DC.PARENT_OBJECT_ID = C.OBJECT_ID
AND DC.PARENT_COLUMN_ID = C.COLUMN_ID
WHERE
PARENT_OBJECT_ID = OBJECT_ID('table1')
AND C.NAME = 'column1';
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 SQL Server 2008 (SP2).
J'ai exécuté le test suivant:
create table tblCheckResult(
ColID int identity
, dtoDateTime Datetimeoffset(0) null
)
go
insert into tblCheckResult (dtoDateTime)
select getdate()
go 10000
checkpoint
ALTER TABLE tblCheckResult
ALTER COLUMN [dtoDateTime] [datetimeoffset](0) NOT NULL
select * from fn_dblog(null,null)
Je crois que cela a à voir avec l'espace réservé que contient le journal au cas où vous annuleriez la transaction. Regardez dans la fonction fn_dblog dans la colonne 'Log Reserve' pour la ligne LOP_BEGIN_XACT et voyez combien d'espace il essaie de réserver.