Database
 sql >> Base de données >  >> RDS >> Database

Minimiser l'impact de l'élargissement d'une colonne IDENTITY - partie 1

[ Partie 1 | Partie 2 | Partie 3 | Partie 4 ]

Un problème que j'ai vu surgir à quelques reprises récemment est le scénario dans lequel vous avez créé une colonne IDENTITY en tant que INT, et que vous approchez maintenant de la limite supérieure et que vous devez l'agrandir (BIGINT). Si votre table est suffisamment grande pour atteindre la limite supérieure d'un nombre entier (plus de 2 milliards), ce n'est pas une opération que vous pouvez effectuer entre le déjeuner et votre pause-café un mardi. Cette série explorera les mécanismes derrière un tel changement et les différentes façons de le réaliser avec des impacts variables sur la disponibilité. Dans la première partie, je voulais examiner de près l'impact physique du changement d'un INT en un BIGINT sans aucune des autres variables.

Que se passe-t-il réellement lorsque vous élargissez un INT ?

INT et BIGINT sont des types de données de taille fixe, donc une conversion de l'un à l'autre doit toucher la page, ce qui en fait une opération de taille de données. Ceci est contre-intuitif, car il semble qu'il ne serait pas possible qu'un changement de type de données de INT à BIGINT nécessite immédiatement l'espace supplémentaire sur la page (et pour une colonne IDENTITY, jamais). En pensant logiquement, il s'agit d'un espace qui ne pourrait être nécessaire que plus tard, lorsqu'une valeur INT existante a été remplacée par une valeur> 4 octets. Mais ce n'est pas comme ça que ça marche aujourd'hui. Créons un tableau simple et voyons :

CREATE TABLE dbo.FirstTest
(
  RowID  int         IDENTITY(1,1), 
  Filler char(2500)  NOT NULL DEFAULT 'x'
);
GO
 
INSERT dbo.FirstTest WITH (TABLOCKX) (Filler)
SELECT TOP (20) 'x' FROM sys.all_columns AS c;
GO

Une simple requête peut me dire la page basse et haute allouée à cet objet, ainsi que le nombre total de pages :

SELECT 
  lo_page    = MIN(allocated_page_page_id), 
  hi_page    = MAX(allocated_page_page_id), 
  page_count = COUNT(*)
FROM sys.dm_db_database_page_allocations
(
  DB_ID(), OBJECT_ID(N'dbo.FirstTest'), NULL, NULL, NULL
);

Maintenant, si j'exécute cette requête avant et après avoir changé le type de données de INT à BIGINT :

ALTER TABLE dbo.FirstTest ALTER COLUMN RowID bigint;

Je vois ces résultats :

-- before:
 
lo_page    hi_page    page_count
-------    -------    ----------
243        303        17
 
-- after:
 
lo_page    hi_page    page_count
-------    -------    ----------
243        319        33

Il est clair que 16 nouvelles pages ont été ajoutées pour faire place à l'espace supplémentaire requis (même si nous savons qu'aucune des valeurs du tableau ne nécessite réellement 8 octets). Mais cela n'a pas été accompli comme vous pourriez le penser - plutôt que d'élargir la colonne sur les pages existantes, les lignes ont été déplacées vers de nouvelles pages, avec des pointeurs laissés à leur place. En regardant la page 243 avant et après (avec la DBCC PAGE non documentée ):

-- ******** Page 243, before: ********
 
Slot 0 Offset 0x60 Length 12
 
Record Type = PRIMARY_RECORD        Record Attributes =  NULL_BITMAP    Record Size = 12
 
Memory Dump @0x000000E34B9FA060
 
0000000000000000:   10000900 01000000 78020000                    ..	.....x...
 
Slot 0 Column 1 Offset 0x4 Length 4 Length (physical) 4
 
RowID = 1                           
 
Slot 0 Column 2 Offset 0x8 Length 1 Length (physical) 1
 
filler = x                          
 
 
-- ******** Page 243, after: ********
 
Slot 0 Offset 0x60 Length 9
 
Record Type = FORWARDING_STUB       Record Attributes =                 Record Size = 9
 
Memory Dump @0x000000E34B9FA060
 
0000000000000000:   04280100 00010078 01                          .(.....x.
Forwarding to  =  file 1 page 296 slot 376

Ensuite, si nous regardons la cible du pointeur, page 296, emplacement 376, nous voyons :

Slot 376 Offset 0x8ca Length 34
 
Record Type = FORWARDED_RECORD      Record Attributes =  NULL_BITMAP VARIABLE_COLUMNS
Record Size = 34                    
Memory Dump @0x000000E33BBFA8CA
 
0000000000000000:   32001100 01000000 78010000 00000000 00030000  2.......x...........
0000000000000014:   01002280 0004f300 00000100 0000               .."...ó.......
Forwarded from  =  file 1 page 243 slot 0                                
 
Slot 376 Column 67108865 Offset 0x4 Length 0 Length (physical) 4
 
DROPPED = NULL                      
 
Slot 376 Column 2 Offset 0x8 Length 1 Length (physical) 1
 
filler = x                          
 
Slot 376 Column 1 Offset 0x9 Length 8 Length (physical) 8
 
RowID = 1

C'est un changement très perturbateur de la structure de la table, évidemment. (Et une observation secondaire intéressante :l'ordre physique des colonnes, RowID et filler, a été inversé sur la page.) L'espace réservé passe de 136 Ko à 264 Ko, et la fragmentation moyenne augmente légèrement de 33,3 % à 40 %. Cet espace n'est pas récupéré par une reconstruction, en ligne ou non, ou une réorganisation, et - comme nous le verrons bientôt - ce n'est pas parce que la table est trop petite pour en bénéficier.

Remarque :cela est vrai même dans les versions les plus récentes de SQL Server 2016 ; alors que de plus en plus d'opérations comme celle-ci ont été améliorées pour devenir des opérations de métadonnées uniquement dans les versions modernes, celle-ci n'a pas encore été corrigée, bien qu'elle soit clairement cela pourrait être - encore une fois, surtout dans le cas où la colonne est une colonne IDENTITY, qui ne peut pas être mise à jour par définition.

Effectuer l'opération avec la nouvelle syntaxe ALTER COLUMN / ONLINE, dont j'ai parlé l'année dernière, donne quelques différences :

-- drop / re-create here
ALTER TABLE dbo.FirstTest ALTER COLUMN RowID bigint WITH (ONLINE = ON);

Maintenant, l'avant et l'après deviennent :

-- before:
 
lo_page    hi_page    page_count
-------    -------    ----------
243        303        17
 
-- after:
 
lo_page    hi_page    page_count
-------    -------    ----------
307        351        17

Dans ce cas, il s'agissait toujours d'une opération de taille de données, mais les pages existantes ont été copiées et recréées grâce à l'option EN LIGNE. Vous vous demandez peut-être pourquoi, lorsque nous avons modifié la taille de la colonne en tant qu'opération EN LIGNE, la table est capable d'entasser plus de données dans le même nombre de pages ? Chaque page est désormais plus dense (moins de lignes mais plus de données par page), au détriment de la dispersion - la fragmentation double de 33,3 % à 66,7 %. L'espace utilisé affiche plus de données dans le même espace réservé (de 72 Ko/136 Ko à 96 Ko/136 Ko).

Et à plus grande échelle ?

Supprimons le tableau, recréons-le et remplissons-le avec beaucoup plus de données :

CREATE TABLE dbo.FirstTest
(
  RowID INT IDENTITY(1,1), 
  filler CHAR(1) NOT NULL DEFAULT 'x'
);
GO
 
INSERT dbo.FirstTest WITH (TABLOCKX) (filler) 
SELECT TOP (5000000) 'x' FROM sys.all_columns AS c1
  CROSS JOIN sys.all_columns AS c2;

Au départ, nous avons maintenant 8 657 pages, un niveau de fragmentation de 0,09 %, et l'espace utilisé est de 69 208 Ko / 69 256 Ko.

Si nous changeons le type de données en bigint, nous passons à 25 630 pages, la fragmentation est réduite à 0,06 % et l'espace utilisé est de 205 032 Ko / 205 064 Ko. Une reconstruction en ligne ne change rien, pas plus qu'une réorganisation. L'ensemble du processus, y compris une reconstruction, prend environ 97 secondes sur ma machine (la population de données a pris toutes les 2 secondes).

Si nous changeons le type de données en bigint en utilisant ONLINE, la hausse n'est que de 11 140 pages, la fragmentation passe à 85,5 % et l'espace utilisé est de 89 088 Ko / 89 160 Ko. Les reconstructions et les réorganisations en ligne ne changent toujours rien. Cette fois, tout le processus ne prend qu'environ une minute. Ainsi, la nouvelle syntaxe conduit définitivement à des opérations plus rapides et à moins d'espace disque supplémentaire, mais à une fragmentation élevée. Je vais le prendre.

Suivant

Je suis sûr que vous regardez mes tests ci-dessus et que vous vous interrogez sur certaines choses. Plus important encore, pourquoi la table est-elle un tas ? Je voulais enquêter sur ce qui arrive réellement à la structure de la page et au nombre de pages sans index, clés ou contraintes brouillant les détails. Vous pouvez également vous demander pourquoi ce changement a été si simple - dans un scénario où vous devez modifier une véritable colonne IDENTITY, il s'agit probablement également de la clé primaire en cluster et des dépendances de clé étrangère dans d'autres tables. Cela introduit définitivement quelques ratés dans le processus. Nous examinerons ces choses de plus près dans le prochain article de la série.

[ Partie 1 | Partie 2 | Partie 3 | Partie 4 ]