Cambiar una columna de NOT NULL a NULL: ¿qué sucede debajo del capó?


25

Tenemos una tabla con 2.3B filas en ella. Nos gustaría cambiar una columna de NOT NULL a NULL. La columna está contenida en un índice (no el índice agrupado o PK). El tipo de datos no está cambiando (es un INT). Solo la nulabilidad. La declaración es la siguiente:

Alter Table dbo.Workflow Alter Column LineId Int NULL

La operación toma más de 10 antes de detenerla (ni siquiera hemos dejado que se complete porque es una operación de bloqueo y estaba tardando demasiado). Probablemente copiaremos la tabla a un servidor de desarrollo para probar cuánto tiempo lleva realmente. Pero, ¿tengo curiosidad por saber si alguien sabe qué está haciendo SQL Server debajo del capó cuando se convierte de NOT NULL a NULL? Además, ¿deberán reconstruirse los índices afectados? El plan de consulta generado no indica lo que está sucediendo.

La tabla en cuestión está agrupada (no es un montón).


2
Creo que debería actualizar el mapa de bits nulo en todas las páginas de datos a nivel de hoja. Y con 2.3B filas, apuesto a que tendría muchas páginas para trabajar. Sin embargo, no estoy muy seguro de esto.
souplex

3
También podría estar ocupado colocando un mapa de bits nulo en el índice. El mapa de bits NULL NO estará presente en un ÍNDICE NO AGRUPADO si todas las columnas que forman parte de la definición del índice están definidas como NOT NULL.
souplex

Respuestas:


27

Como aludió @Souplex en los comentarios, una posible explicación podría ser si esta columna es la primera NULLcolumna de tabla en el índice no agrupado en el que participa.

Para la siguiente configuración

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 muestra que el índice no agrupado ixtiene 248 páginas de hoja y una sola página raíz.

Una fila típica en una página de índice se ve así

ingrese la descripción de la imagen aquí

Y en la página raíz

ingrese la descripción de la imagen aquí

Entonces corriendo ...

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

Devuelto

+-----------------+--------------------+-------------+----------------+
|    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 |
+-----------------+--------------------+-------------+----------------+

Verificando la hoja de índice nuevamente, las filas ahora se ven como

ingrese la descripción de la imagen aquí

y las filas en las páginas de nivel superior como a continuación.

ingrese la descripción de la imagen aquí

Cada fila se ha actualizado y ahora contiene dos bytes para el recuento de columnas junto con otro byte para NULL_BITMAP.

Debido al ancho de fila adicional, el índice no agrupado ahora tiene 285 páginas de hoja y ahora dos páginas de nivel intermedio junto con la página raíz.

El plan de ejecución para el

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

se ve de la siguiente manera

ingrese la descripción de la imagen aquí

Esto crea una nueva copia del índice en lugar de actualizar la existente y la necesidad de dividir las páginas.


9

Definitivamente volverá a crear el índice no agrupado y no solo actualizará los metadatos. Esto se prueba en SQL 2014 y realmente no debería probarse en un sistema de producción:

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);

Y ahora para la parte divertida:

DBCC IND (0, z, -1)

Esto nos dará las páginas de la base de datos donde se almacenan la tabla y el índice no agrupado.

Encuentre PagePIDdónde IndexIDes 2 y PageTypees 2, y luego haga lo siguiente:

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

y entonces:

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

Observe que hay un mapa de bits nulo en el encabezado:

Extracto de encabezado de página

Ahora hagamos:

alter table z alter Column b int null;

Si está realmente impaciente, puede intentar ejecutar el dbcc pagecomando nuevamente, pero fallará, así que verifiquemos la asignación nuevamente con DBCC IND (0, z, -1). La página se habrá movido como por arte de magia.

Por lo tanto, cambiar la nulabilidad de una columna afectará el almacenamiento de índices no agrupados que cubren esa columna, ya que los metadatos deben actualizarse y no debería necesitar reconstruir los índices después.


Se ALTER TABLE ... ALTER COLUMN ...pueden realizar muchas operaciones a ONLINEpartir de SQL Server 2016, pero:

ALTER TABLE (Transact-SQL)

  • La alteración de una columna de NOT NULLa NULLno se admite como una operación en línea cuando los índices no agrupados hacen referencia a la columna alterada.
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.