web-dev-qa-db-fra.com

Changer une colonne de NOT NULL à NULL - Que se passe-t-il sous le capot?

Nous avons un tableau avec 2,3 milliards de lignes. Nous aimerions changer une colonne de NOT NULL à NULL. La colonne est contenue dans un index (pas l'index cluster ou PK). Le type de données ne change pas (c'est un INT). Juste la nullité. La déclaration est la suivante:

Alter Table dbo.Workflow Alter Column LineId Int NULL

L'opération prend plus de 10 avant de l'arrêter (nous ne l'avons même pas encore laissée se terminer car c'est une opération de blocage et prenait trop de temps). Nous allons probablement copier la table sur un serveur de développement et tester le temps qu'il faut réellement. Mais, je suis curieux de savoir si quelqu'un sait ce que SQL Server fait sous le capot lors de la conversion de NOT NULL en NULL? De plus, les index affectés devront-ils être reconstruits? Le plan de requête généré n'indique pas ce qui se passe.

La table en question est groupée (pas un tas).

25
Randy Minder

Comme mentionné par @Souplex dans les commentaires, une explication possible pourrait être si cette colonne est la première colonne capable de NULL dans l'index non cluster auquel elle participe.

Pour la configuration suivante

CREATE TABLE Foo
  (
     A UNIQUEIDENTIFIER NOT NULL DEFAULT NEWSEQUENTIALID() PRIMARY KEY,
     B CHAR(1) NOT NULL DEFAULT 'B'
  )

CREATE NONCLUSTERED INDEX ix
  ON Foo(B);

INSERT INTO Foo
            (B)
SELECT TOP 100000 'B'
FROM   master..spt_values v1,
       master..spt_values v2 

sys.dm_db_index_physical_stats montre que l'index non clusterisé ix a 248 pages feuilles et une seule page racine.

Une ligne typique dans une page feuille d'index ressemble à

enter image description here

Et dans la page racine

enter image description here

Puis en cours d'exécution ...

CHECKPOINT;

GO

ALTER TABLE Foo ALTER COLUMN B  CHAR(1) NULL;


SELECT Operation, 
       Context,
       ROUND(SUM([Log Record Length]) / 1024.0,1) AS [Log KB],
       COUNT(*) as [OperationCount]
FROM sys.fn_dblog(NULL,NULL)
WHERE AllocUnitName = 'dbo.Foo.ix'
GROUP BY Operation, Context

Revenu

+-----------------+--------------------+-------------+----------------+
|    Operation    |      Context       |   Log KB    | OperationCount |
+-----------------+--------------------+-------------+----------------+
| LOP_SET_BITS    | LCX_GAM            | 4.200000    |             69 |
| LOP_FORMAT_PAGE | LCX_IAM            | 0.100000    |              1 |
| LOP_SET_BITS    | LCX_IAM            | 4.200000    |             69 |
| LOP_FORMAT_PAGE | LCX_INDEX_INTERIOR | 8.700000    |              3 |
| LOP_FORMAT_PAGE | LCX_INDEX_LEAF     | 2296.200000 |            285 |
| LOP_MODIFY_ROW  | LCX_PFS            | 16.300000   |            189 |
+-----------------+--------------------+-------------+----------------+

En vérifiant à nouveau la feuille d'index, les lignes ressemblent maintenant à

enter image description here

et les lignes dans les pages de niveau supérieur comme ci-dessous.

enter image description here

Chaque ligne a été mise à jour et contient désormais deux octets pour le nombre de colonnes ainsi qu'un autre octet pour NULL_BITMAP.

En raison de la largeur de ligne supplémentaire, l'index non cluster a maintenant 285 pages feuilles et maintenant deux pages de niveau intermédiaire avec la page racine.

Le plan d'exécution du

 ALTER TABLE Foo ALTER COLUMN B  CHAR(1) NULL;

ressemble à ceci

enter image description here

Cela crée une toute nouvelle copie de l'index plutôt que de mettre à jour l'existant et de devoir fractionner les pages.

27
Martin Smith

Il va certainement recréer l'index non clusterisé et pas seulement mettre à jour les métadonnées. Ceci est testé sur SQL 2014 et ne devrait vraiment pas être testé sur un système de production:

CREATE TABLE [z](
    [a] [int] IDENTITY(1,1) NOT NULL,
    [b] [int] NOT NULL,
 CONSTRAINT [c_a] PRIMARY KEY CLUSTERED  ([a] ASC))
go
CREATE NONCLUSTERED INDEX [nc_b] on z (b asc)
GO
insert into z (b)
values (1);

Et maintenant pour la partie amusante:

DBCC IND (0, z, -1)

Cela nous donnera les pages de base de données où la table et l'index non cluster sont stockés.

Recherchez PagePIDIndexID vaut 2 et PageType vaut 2, puis procédez comme suit:

DBCC TRACEON(3604) --are you sure that you are allowed to do this?

et alors:

dbcc page (0, 1, PagePID, 3) with tableresults

Notez qu'il y a un bitmap nul dans l'en-tête:

Page header extract

Faisons maintenant:

alter table z alter Column b int null;

Si vous êtes vraiment impatient, vous pouvez essayer d'exécuter à nouveau la commande dbcc page Mais elle échouera, alors vérifions à nouveau l'allocation avec DBCC IND (0, z, -1). La page se déplacera comme par magie.

Ainsi, la modification de la nullité d'une colonne affectera le stockage des index non cluster qui couvrent cette colonne, car les métadonnées doivent être mises à jour et vous ne devriez pas avoir besoin de reconstruire les index par la suite.


De nombreuses opérations ALTER TABLE ... ALTER COLUMN ... Peuvent être effectuées ONLINE à partir de SQL Server 2016, mais:

ALTER TABLE (Transact-SQL)

  • La modification d'une colonne de NOT NULL En NULL n'est pas prise en charge en tant qu'opération en ligne lorsque la colonne modifiée est référencée par des index non clusterisés.
9
Spörri