sql >> Base de Datos >  >> RDS >> Database

Minimizar el impacto de ampliar una columna de IDENTIDAD - parte 1

[ Parte 1 | Parte 2 | Parte 3 | Parte 4 ]

Un problema que he visto surgir varias veces recientemente es el escenario en el que ha creado una columna IDENTIDAD como INT, y ahora se está acercando al límite superior y necesita agrandarla (BIGINT). Si su mesa es lo suficientemente grande como para alcanzar el límite superior de un número entero (más de 2 mil millones), esta no es una operación que pueda realizar entre el almuerzo y su descanso para tomar café un martes. Esta serie explorará la mecánica detrás de dicho cambio y las diferentes formas de hacerlo realidad con diferentes impactos en el tiempo de actividad. En la primera parte, quería observar de cerca el impacto físico de cambiar un INT a un BIGINT sin ninguna de las otras variables.

¿Qué sucede realmente cuando amplías un INT?

INT y BIGINT son tipos de datos de tamaño fijo, por lo tanto, una conversión de uno a otro tiene que tocar la página, lo que hace que esta sea una operación de tamaño de datos. Esto es contrario a la intuición, porque parece que no sería posible que un cambio de tipo de datos de INT a BIGINT requiera el espacio adicional en la página inmediatamente (y para una columna IDENTIDAD, nunca). Pensando lógicamente, este es un espacio que posiblemente no se necesite hasta más tarde, cuando un valor INT existente se cambió a un valor> 4 bytes. Pero no es así como funciona hoy. Creemos una tabla simple y veamos:

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

Una simple consulta puede decirme la página alta y baja asignada a este objeto, así como el número total de páginas:

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

Ahora, si ejecuto esa consulta antes y después de cambiar el tipo de datos de INT a BIGINT:

ALTER TABLE dbo.FirstTest ALTER COLUMN RowID bigint;

Veo estos resultados:

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

Está claro que se agregaron 16 páginas nuevas para dejar espacio para el espacio adicional requerido (aunque sabemos que ninguno de los valores en la tabla en realidad requiere 8 bytes). Pero esto en realidad no se logró de la manera que podría pensar:en lugar de ampliar la columna en las páginas existentes, las filas se movieron a nuevas páginas, dejando los punteros en su lugar. Mirando la página 243 antes y después (con la DBCC PAGE no documentada ):

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

Luego, si observamos el objetivo del puntero, página 296, ranura 376, vemos:

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

Este es un cambio muy disruptivo en la estructura de la mesa, obviamente. (Y una observación adicional interesante:el orden físico de las columnas, ID de fila y relleno, se invirtió en la página). El espacio reservado salta de 136 KB a 264 KB, y la fragmentación promedio aumenta modestamente del 33,3 % al 40 %. Este espacio no se recupera mediante una reconstrucción, en línea o no, o una reorganización y, como veremos en breve, esto no se debe a que la mesa sea demasiado pequeña para beneficiarse.

Nota:esto es cierto incluso en las compilaciones más recientes de SQL Server 2016; aunque cada vez más operaciones como esta se han mejorado para convertirse en operaciones solo de metadatos en las versiones modernas, esta aún no se ha solucionado, aunque claramente podría serlo, de nuevo, especialmente en el caso de que la columna sea una columna IDENTIDAD, que no se puede actualizar por definición.

Realizar la operación con la nueva sintaxis ALTER COLUMN / ONLINE, de la que hablé el año pasado, arroja algunas diferencias:

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

Ahora el antes y el después se convierte en:

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

En este caso, todavía era una operación de tamaño de datos, pero las páginas existentes se copiaron y volvieron a crear debido a la opción EN LÍNEA. Quizás se pregunte por qué, cuando cambiamos el tamaño de la columna como una operación EN LÍNEA, la tabla puede incluir más datos en la misma cantidad de páginas. Cada página ahora es más densa (menos filas pero más datos por página), a costa de la dispersión:la fragmentación se duplica del 33,3 % al 66,7 %. El espacio utilizado muestra más datos en el mismo espacio reservado (de 72 KB/136 KB a 96 KB/136 KB).

¿Y a mayor escala?

Dejemos caer la tabla, volvamos a crearla y llenémosla con muchos más datos:

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;

De inicio tenemos ahora 8.657 páginas, un nivel de fragmentación del 0,09% y el espacio utilizado es de 69.208 KB / 69.256 KB.

Si cambiamos el tipo de datos a bigint, saltamos a 25.630 páginas, la fragmentación se reduce al 0,06% y el espacio utilizado es de 205.032 KB / 205.064 KB. Una reconstrucción en línea no cambia nada, ni tampoco una reorganización. Todo el proceso, incluida la reconstrucción, toma alrededor de 97 segundos en mi máquina (el llenado de datos tomó 2 segundos).

Si cambiamos el tipo de datos a bigint usando ONLINE, el aumento es solo de 11,140 páginas, la fragmentación llega al 85.5% y el espacio utilizado es de 89,088 KB / 89160 KB. Las reconstrucciones y reorganizaciones en línea aún no cambian nada. Esta vez, todo el proceso solo toma alrededor de un minuto. Entonces, la nueva sintaxis definitivamente conduce a operaciones más rápidas y menos espacio adicional en disco, pero con una alta fragmentación. Me lo llevo.

A continuación

Estoy seguro de que está mirando mis pruebas anteriores y se pregunta algunas cosas. Lo más importante, ¿por qué la mesa es un montón? Quería investigar qué sucede realmente con la estructura de la página y el recuento de páginas sin índices, claves o restricciones que dificulten los detalles. También puede preguntarse por qué este cambio fue tan fácil:en un escenario en el que tiene que cambiar una columna de IDENTIDAD real, probablemente también sea la clave principal agrupada y tenga dependencias de clave externa en otras tablas. Esto definitivamente introduce algunos contratiempos en el proceso. Echaremos un vistazo más de cerca a estas cosas en la próxima publicación de la serie.

[ Parte 1 | Parte 2 | Parte 3 | Parte 4 ]