ALTER TABLE… DROP COLUMN est-il vraiment une opération de métadonnées uniquement?

11

J'ai trouvé plusieurs sources qui indiquent ALTER TABLE ... DROP COLUMN est une opération de métadonnées uniquement.

La source

Comment se peut-il? Les données pendant une DROP COLUMN n'ont-elles pas besoin d'être purgées des index non groupés sous-jacents et des index / tas cluster?

De plus, pourquoi les documents Microsoft impliquent-ils qu'il s'agit d'une opération entièrement journalisée?

Les modifications apportées à la table sont enregistrées et entièrement récupérables. Les modifications qui affectent toutes les lignes des grandes tables, telles que la suppression d'une colonne ou, sur certaines éditions de SQL Server, l'ajout d'une colonne NOT NULL avec une valeur par défaut, peuvent prendre beaucoup de temps pour se terminer et générer de nombreux enregistrements de journal . Exécutez ces instructions ALTER TABLE avec le même soin que toute instruction INSERT, UPDATE ou DELETE qui affecte de nombreuses lignes.

Comme question secondaire: comment le moteur garde-t-il les colonnes supprimées si les données ne sont pas supprimées des pages sous-jacentes?

George.Palacios
la source
2
Eh bien, je pense que la langue a survécu à travers de nombreuses versions du produit et de nombreuses itérations de la documentation. Au fil du temps, de plus en plus d'opérations impliquant des colonnes sont devenues des modifications en ligne / uniquement des métadonnées. C'est peut-être un mauvais exemple spécifique maintenant, mais le but de la phrase est simplement de vous avertir que, en général, certaines opérations de modification peuvent être des opérations de taille de données dans certains scénarios, plutôt que d'énumérer chaque scénario spécifique.
Aaron Bertrand

Réponses:

14

Dans certaines circonstances, la suppression d'une colonne peut être une opération de métadonnées uniquement. Les définitions de colonne pour une table donnée ne sont pas incluses dans chaque page où les lignes sont stockées, les définitions de colonne ne sont stockées que dans les métadonnées de la base de données, y compris sys.sysrowsets, sys.sysrscols, etc.

Lors de la suppression d'une colonne qui n'est référencée par aucun autre objet, le moteur de stockage marque simplement la définition de la colonne comme n'étant plus présente en supprimant les détails pertinents de diverses tables système. L'action de supprimer les métadonnées invalide le cache de procédure, ce qui nécessite une recompilation chaque fois qu'une requête référence ultérieurement cette table. Étant donné que la recompilation renvoie uniquement les colonnes qui existent actuellement dans la table, les détails de la colonne pour la colonne supprimée ne sont même jamais demandés; le moteur de stockage ignore les octets stockés dans chaque page pour cette colonne, comme si la colonne n'existe plus.

Lorsqu'une opération DML suivante se produit sur la table, les pages concernées sont réécrites sans les données de la colonne supprimée. Si vous reconstruisez un index cluster ou un segment de mémoire, tous les octets de la colonne supprimée ne sont naturellement pas réécrits sur la page sur le disque. Cela répartit efficacement la charge de chute de la colonne au fil du temps, ce qui la rend moins perceptible.

Dans certaines circonstances, vous ne pouvez pas supprimer une colonne, par exemple lorsque la colonne est incluse dans un index ou lorsque vous avez créé manuellement un objet de statistiques pour la colonne. J'ai écrit un article de blog montrant l'erreur qui se présente lors d'une tentative de modification d'une colonne avec un objet de statistiques créé manuellement. La même sémantique s'applique lors de la suppression d'une colonne - si la colonne est référencée par un autre objet, elle ne peut pas simplement être supprimée. L'objet de référence doit d'abord être modifié, puis la colonne peut être supprimée.

C'est assez facile à montrer en regardant le contenu du journal des transactions après avoir supprimé une colonne. Le code ci-dessous crée une table avec une seule colonne de 8 000 caractères longs. Il ajoute une ligne, puis la supprime et affiche le contenu du journal des transactions applicable à l'opération de suppression. Les enregistrements du journal montrent les modifications apportées aux différentes tables système où les définitions de table et de colonne sont stockées. Si les données de colonne étaient réellement supprimées des pages allouées à la table, vous verriez des enregistrements de journal enregistrant les données de page réelles; il n'y a pas de tels enregistrements.

DROP TABLE IF EXISTS dbo.DropColumnTest;
GO
CREATE TABLE dbo.DropColumnTest
(
    rid int NOT NULL
        CONSTRAINT DropColumnTest_pkc
        PRIMARY KEY CLUSTERED
    , someCol varchar(8000) NOT NULL
);

INSERT INTO dbo.DropColumnTest (rid, someCol)
SELECT 1, REPLICATE('Z', 8000);
GO

DECLARE @startLSN nvarchar(25);

SELECT TOP(1) @startLSN = dl.[Current LSN]
FROM sys.fn_dblog(NULL, NULL) dl
ORDER BY dl.[Current LSN] DESC;

DECLARE @a int = CONVERT(varbinary(8), '0x' + CONVERT(varchar(10),      LEFT(@startLSN, 8), 0), 1)
      , @b int = CONVERT(varbinary(8), '0x' + CONVERT(varchar(10), SUBSTRING(@startLSN, 10, 8), 0), 1)
      , @c int = CONVERT(varbinary(8), '0x' + CONVERT(varchar(10),     RIGHT(@startLSN, 4), 0), 1);

SELECT @startLSN = CONVERT(varchar(8), @a, 1) 
    + ':' + CONVERT(varchar(8), @b, 1) 
    + ':' + CONVERT(varchar(8), @c, 1)

ALTER TABLE dbo.DropColumnTest DROP COLUMN someCol;

SELECT *
FROM sys.fn_dblog(@startLSN, NULL)


--modify an existing data row 
SELECT TOP(1) @startLSN = dl.[Current LSN]
FROM sys.fn_dblog(NULL, NULL) dl
ORDER BY dl.[Current LSN] DESC;

SET @a = CONVERT(varbinary(8), '0x' + CONVERT(varchar(10),      LEFT(@startLSN, 8), 0), 1);
SET @b = CONVERT(varbinary(8), '0x' + CONVERT(varchar(10), SUBSTRING(@startLSN, 10, 8), 0), 1);
SET @c = CONVERT(varbinary(8), '0x' + CONVERT(varchar(10),     RIGHT(@startLSN, 4), 0), 1);

SELECT @startLSN = CONVERT(varchar(8), @a, 1) 
    + ':' + CONVERT(varchar(8), @b, 1) 
    + ':' + CONVERT(varchar(8), @c, 1)

UPDATE dbo.DropColumnTest SET rid = 2;

SELECT *
FROM sys.fn_dblog(@startLSN, NULL)

(La sortie est trop grande pour être affichée ici, et dbfiddle.uk ne me permettra pas d'accéder à fn_dblog)

Le premier ensemble de sorties affiche le journal suite à la suppression de la colonne par l'instruction DDL. Le deuxième ensemble de sorties affiche le journal après l'exécution de l'instruction DML où nous mettons à jour la ridcolonne. Dans le deuxième jeu de résultats, nous voyons des enregistrements de journal indiquant une suppression par rapport à dbo.DropColumnTest, suivie d'une insertion dans dbo.DropColumnTest. Chaque longueur d'enregistrement de journal est 8116, indiquant que la page réelle a été mise à jour.

Comme vous pouvez le voir sur la sortie de la fn_dblogcommande dans le test ci-dessus, toute l'opération est entièrement enregistrée. Cela vaut pour une récupération simple, ainsi qu'une récupération complète. La terminologie «entièrement enregistré» peut être mal interprétée car la modification des données n'est pas enregistrée. Ce n'est pas ce qui se passe - la modification est enregistrée et peut être entièrement annulée. Le journal n'enregistre simplement que les pages qui ont été touchées, et comme aucune des pages de données de la table n'a été enregistrée par l'opération DDL, la DROP COLUMNet toute annulation qui peut se produire se produiront extrêmement rapidement, quelle que soit la taille de la table.

Pour la science , le code suivant videra les pages de données du tableau inclus dans le code ci-dessus, en utilisant le DBCC PAGEstyle "3". Le style "3" indique que nous voulons l'en- tête de page plus une interprétation détaillée par ligne . Le code utilise un curseur pour afficher les détails de chaque page du tableau, vous pouvez donc vous assurer de ne pas l'exécuter sur un grand tableau.

DBCC TRACEON(3604); --directs out from DBCC commands to the console, instead of the error log
DECLARE @dbid int = DB_ID();
DECLARE @fileid int;
DECLARE @pageid int;
DECLARE cur CURSOR LOCAL FORWARD_ONLY STATIC READ_ONLY
FOR
SELECT dpa.allocated_page_file_id
    , dpa.allocated_page_page_id
FROM sys.schemas s  
    INNER JOIN sys.objects o ON o.schema_id = s.schema_id
CROSS APPLY sys.dm_db_database_page_allocations(DB_ID(), o.object_id, NULL, NULL, 'DETAILED') dpa
WHERE o.name = N'DropColumnTest'
    AND s.name = N'dbo'
    AND dpa.page_type_desc = N'DATA_PAGE';
OPEN cur;
FETCH NEXT FROM cur INTO @fileid, @pageid;
WHILE @@FETCH_STATUS = 0
BEGIN
    DBCC PAGE (@dbid, @fileid, @pageid, 3);
    FETCH NEXT FROM cur INTO @fileid, @pageid;
END
CLOSE cur;
DEALLOCATE cur;
DBCC TRACEOFF(3604);

En regardant la sortie de la première page de ma démo (après la suppression de la colonne, mais avant la mise à jour de la colonne), je vois ceci:

PAGE: (1: 100104)


TAMPON:


BUF @ 0x0000021793E42040

bpage = 0x000002175A7A0000 bhash = 0x0000000000000000 bpageno = (1: 100104)
bdbid = 10 breferences = 1 bcputicks = 0
bsampleCount = 0 bUse1 = 13760 bstat = 0x10b
blog = 0x212121cc bnext = 0x0000000000000000 bDirtyContext = 0x000002175004B640
bstat2 = 0x0                        

EN-TÊTE DE PAGE:


Page @ 0x000002175A7A0000

m_pageId = (1: 100104) m_headerVersion = 1 m_type = 1
m_typeFlagBits = 0x0 m_level = 0 m_flagBits = 0xc000
m_objId (AllocUnitId.idObj) = 300 m_indexId (AllocUnitId.idInd) = 256 
Métadonnées: AllocUnitId = 72057594057588736                                
Métadonnées: PartitionId = 72057594051756032 Métadonnées: IndexId = 1
Métadonnées: ObjectId = 174623665 m_prevPage = (0: 0) m_nextPage = (0: 0)
pminlen = 8 m_slotCnt = 1 m_freeCnt = 79
m_freeData = 8111 m_reservedCnt = 0 m_lsn = (616: 14191: 25)
m_xactReserved = 0 m_xdesId = (0: 0) m_ghostRecCnt = 0
m_tornBits = 0 ID de fragment de base de données = 1                      

Statut d'allocation

GAM (1: 2) = SGAM ATTRIBUÉ (1: 3) = NON ATTRIBUÉ          
PFS (1: 97056) = 0x40 ALLOCÉ 0_PCT_FULL DIFF (1: 6) = CHANGÉ
ML (1: 7) = NON MIN_LOGGED           

Emplacement 0 Décalage 0x60 Longueur 8015

Type d'enregistrement = PRIMARY_RECORD Attributs d'enregistrement = NULL_BITMAP VARIABLE_COLUMNS
Taille d'enregistrement = 8015                  
Vidage de la mémoire @ 0x000000B75227A060

0000000000000000: 30000800 01000000 02000001 004f1f5a 5a5a5a5a 0 ............ O.ZZZZZ
0000000000000014: 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
.
.
.
0000000000001F2C: 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a ZZZZZZZZZZZZZZZZZZZZZ
0000000000001F40: 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a ZZZZZZZZZZZZZZZZ

Emplacement 0 Colonne 1 Décalage 0x4 Longueur 4 Longueur (physique) 4

débarrasser = 1                             

Emplacement 0 Colonne 67108865 Décalage 0xf Longueur 0 Longueur (physique) 8000

DÉPOSÉS = NUL                      

Emplacement 0 Décalage 0x0 Longueur 0 Longueur (physique) 0

KeyHashValue = (8194443284a0)       

J'ai supprimé la plupart du vidage de page brut de la sortie ci-dessus pour plus de brièveté. À la fin de la sortie, vous verrez ceci pour la ridcolonne:

Emplacement 0 Colonne 1 Décalage 0x4 Longueur 4 Longueur (physique) 4

débarrasser = 1                             

La dernière ligne ci-dessus rid = 1,, renvoie le nom de la colonne et la valeur actuelle stockée dans la colonne de la page.

Ensuite, vous verrez ceci:

Emplacement 0 Colonne 67108865 Décalage 0xf Longueur 0 Longueur (physique) 8000

DÉPOSÉS = NUL                      

La sortie montre que l'emplacement 0 contient une colonne supprimée, en vertu du DELETEDtexte où le nom de la colonne serait normalement. La valeur de la colonne est renvoyée NULLcar la colonne a été supprimée. Cependant, comme vous pouvez le voir dans les données brutes, la valeur longue de 8 000 caractères REPLICATE('Z', 8000), pour cette colonne existe toujours sur la page. Voici un exemple de cette partie de la sortie DBCC PAGE:

0000000000001EDC: 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a ZZZZZZZZZZZZZZZZZZZZZ
0000000000001EF0: 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a ZZZZZZZZZZZZZZZZZZZZZ
0000000000001F04: 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a ZZZZZZZZZZZZZZZZZZZZZ
0000000000001F18: 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a 5a5a5a5a ZZZZZZZZZZZZZZZZZZZZZ
Max Vernon
la source