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

A veces PUEDE aumentar el tamaño de una columna en el lugar

El año pasado, Andy Mallon escribió en su blog sobre cómo aumentar el tamaño de una columna de int a bigint sin tiempo de inactividad. (Por qué esta no es una operación solo de metadatos en las versiones modernas de SQL Server me supera, pero esa es otra publicación).

Por lo general, cuando nos ocupamos de este problema, son tablas anchas y masivas (tanto en el número de filas como en el tamaño total), y la columna que necesitamos cambiar es la única columna principal en la clave de agrupación. Por lo general, también hay otras complicaciones involucradas:restricciones de clave externa entrante, muchos índices no agrupados y una base de datos ocupada que es ultrasensible a la actividad de registro (porque está involucrada en el seguimiento de cambios, la replicación, los grupos de disponibilidad o los tres). ).

Por esta razón, debemos adoptar un enfoque como el descrito por Andy, en el que construimos una tabla paralela con el nuevo esquema, creamos activadores para mantener ambas copias sincronizadas y luego lote/rellenamos al ritmo de ese equipo hasta que estén listos para intercambiar en la copia como el trato real.

¡Pero soy flojo!

Hay algunos casos en los que puede cambiar la columna directamente, si puede permitirse una pequeña ventana de tiempo de inactividad/bloqueo, y se convierte en una operación mucho más simple. La semana pasada surgió uno de esos casos, con una tabla de más de 1 TB, pero solo 100 000 filas. Casi todos los datos estaban fuera de fila (LOB), podían permitirse una pequeña ventana de tiempo de inactividad si era necesario, y planeaban desactivar el seguimiento de cambios y volver a configurarlo de todos modos. Confiado en que volver a crear el PK agrupado no tendría que tocar los datos LOB (mucho), sugerí que este podría ser un caso en el que solo podemos aplicar el cambio directamente.

En un escenario aislado (sin claves foráneas entrantes, sin índices adicionales, sin actividades que dependan del lector de registro y sin preocupaciones sobre la concurrencia), realicé algunas pruebas para ver, en el vacío, qué requeriría este cambio en términos de duración. e impacto en el registro de transacciones. La pregunta principal que no sabía cómo responder de antemano era:"¿Cuál es el costo incremental de actualizar las tablas en el lugar cuando hay grandes cantidades de datos no clave?"

Voy a tratar de empaquetar mucho en una publicación aquí. Hice muchas pruebas y todo está relacionado, incluso si no todos los escenarios de prueba se aplican a usted. Por favor, tengan paciencia conmigo.

Las mesas

Creé 6 tablas, incluida una línea de base que solo tenía la columna clave, una tabla con 4K almacenados en fila y luego cuatro tablas, cada una con una columna varchar(max) poblada con cantidades variables de datos de cadena (4K, 16K, 64K y 256K).

CREATE TABLE dbo.withJustId
(
  id int NOT NULL,
  CONSTRAINT pk_withJustId PRIMARY KEY CLUSTERED (id)
);
 
CREATE TABLE dbo.withoutLob
(
  id int NOT NULL,
  extradata varchar(4000)  NOT NULL DEFAULT (REPLICATE('x', 4000)),
  CONSTRAINT pk_withoutLob PRIMARY KEY CLUSTERED (id)
);
 
CREATE TABLE dbo.withLob004
(
  id int NOT NULL, 
  extradata varchar(max)   NOT NULL DEFAULT (REPLICATE('x', 4000)),
  CONSTRAINT pk_withLob004 PRIMARY KEY CLUSTERED (id)
);
 
CREATE TABLE dbo.withLob016
(
  id int NOT NULL, 
  extradata varchar(max)   NOT NULL DEFAULT (REPLICATE(CONVERT(varchar(max),'x'), 16000)),
  CONSTRAINT pk_withLob016 PRIMARY KEY CLUSTERED (id)
);
 
CREATE TABLE dbo.withLob064 
(
  id int NOT NULL, 
  extradata varchar(max)   NOT NULL DEFAULT (REPLICATE(CONVERT(varchar(max),'x'), 64000)),
  CONSTRAINT pk_withLob064 PRIMARY KEY CLUSTERED (id)
);
 
CREATE TABLE dbo.withLob256 
(
  id int NOT NULL, 
  extradata varchar(max)   NOT NULL DEFAULT (REPLICATE(CONVERT(varchar(max),'x'), 256000)),
  CONSTRAINT pk_withLob256 PRIMARY KEY CLUSTERED (id)
);

Rellené cada uno con 100.000 filas:

INSERT dbo.withJustId (id)
  SELECT TOP (100000) id = ROW_NUMBER() OVER (ORDER BY c1.name) 
  FROM sys.all_columns AS c1 CROSS JOIN sys.all_objects;
 
INSERT dbo.withoutLob (id) SELECT id FROM dbo.withJustId;
INSERT dbo.withLob004 (id) SELECT id FROM dbo.withJustId;
INSERT dbo.withLob016 (id) SELECT id FROM dbo.withJustId;
INSERT dbo.withLob064 (id) SELECT id FROM dbo.withJustId;
INSERT dbo.withLob256 (id) SELECT id FROM dbo.withJustId;

Reconozco que lo anterior no es realista; ¿Con qué frecuencia tenemos una tabla que es solo un identificador + datos LOB? Volví a ejecutar las pruebas con estas cuatro columnas adicionales para dar a las páginas de datos que no son LOB un poco más de sustancia del mundo real:

     fill1  char(320) NOT NULL DEFAULT ('x'),
     count1 int       NOT NULL DEFAULT (0),
     count2 int       NOT NULL DEFAULT (0),
     dt     datetime2 NOT NULL DEFAULT sysutcdatetime(),

Estas tablas son solo un poco más grandes en términos de tamaño general, pero el aumento proporcional en la cantidad de datos que no son LOB (no se ilustra en este gráfico) es la diferencia grande pero oculta:

Tamaño reservado de tablas, en GB

Las pruebas

Luego cronometré y recopilé datos de registro para cada una de estas operaciones (con y sin ONLINE = ON ) contra cada variación de la tabla:

ALTER TABLE dbo.<name> DROP CONSTRAINT pk_<name>;
 
ALTER TABLE dbo.<name> ALTER COLUMN id bigint NOT NULL; -- WITH (ONLINE = ON);
 
ALTER TABLE dbo.<name> ADD CONSTRAINT pk_<name> PRIMARY KEY CLUSTERED (id);

En realidad, utilicé SQL dinámico para generar todas estas pruebas, de modo que no tuviera que jugar manualmente con los scripts antes de cada prueba.
En otra publicación, compartiré el SQL dinámico que usé para generar esas pruebas y recopilaré los tiempos en cada paso.

A modo de comparación, también probé el método de Andy (aunque sin procesamiento por lotes y solo en la versión reducida de la tabla):

CREATE TABLE dbo.<name>_copy ( id bigint NOT NULL 
   -- <, extradata column when relevant >
   CONSTRAINT pk_copy_<name> PRIMARY KEY CLUSTERED (id));
 
INSERT dbo.<name>_copy SELECT * FROM dbo.<name>;
 
EXEC sys.sp_rename N'dbo.<name>',      N'dbo.<name>_old', N'OBJECT';
EXEC sys.sp_rename N'dbo.<name>_copy', N'dbo.<name>',     N'OBJECT';

Me salté las mesas más anchas aquí; No quería presentar la complejidad de codificar y medir operaciones por lotes. El problema obvio aquí es que, a diferencia de cambiar la columna en el lugar, con el método de sombra tiene que copiar cada byte de esos datos LOB. El procesamiento por lotes puede minimizar el gran impacto de tratar de hacer eso en una sola transacción, pero toda esa mezcla eventualmente tendrá que volver a hacerse aguas abajo. El procesamiento por lotes en el origen no puede controlar por completo cuánto dañará en el destino.

Los resultados

Los primeros resultados que voy a mostrar son solo las duraciones promedio de las modificaciones en el lugar, para las 12 configuraciones de mesa y con y sin ONLINE = ON :

Duración, en segundos, de la modificación de la columna en el lugar

Realizar esto como una operación en línea lleva más tiempo (200 segundos en el peor de los casos), pero no bloquea a los usuarios. Parece aumentar junto con el tamaño, pero no de forma lineal. Realizar esta operación fuera de línea provoca el bloqueo, pero es mucho más rápido y no cambia tan drásticamente a medida que la tabla crece (incluso en el tamaño más grande, esto sucedió en aproximadamente un minuto).

Comparar estas operaciones en el lugar con la operación de intercambio y colocación es difícil utilizando un gráfico de líneas debido a la enorme diferencia de escala. En su lugar, voy a mostrar un gráfico de barras horizontales para la duración involucrada con cada configuración de mesa. Cuando la recreación sea más rápida, pintaré el fondo de esa fila de verde; cuando es más lento (o se encuentra entre los métodos sin conexión y en línea), probablemente no necesite hacerlo, pero pintaré de rojo el fondo de esa fila.

Tamaño LOB | Enfoque | Configuración de la tabla Duración (segundos)
Solo identificación ALTER Sin conexión Tabla más delgada (10 MB) 8.8
Tabla más ancha (30 MB) 6.3
ALTERAR en línea Mesa más delgada 11.0
Mesa más ancha 13.6
Recrear Mesa más delgada 3.4
varchar 4K Sin conexión Tabla más delgada (390 MB) 16.6
Tabla más ancha (780 MB) 14.0
En línea Mesa más delgada 30.4
Mesa más ancha 48.6
Recrear Mesa más delgada 1290,0
máximo 4k Sin conexión Tabla más delgada (390 MB) 33.1
Tabla más ancha (780 MB) 32.1
En línea Mesa más delgada 81.9
Mesa más ancha 103.3
Recrear Mesa más delgada 28.9
máximo 16k Sin conexión Tabla más delgada (1,6 GB) 53.3
Tabla más ancha (1,7 GB) 46.7
En línea Mesa más delgada 130.9
Mesa más ancha 150.2
Recrear Mesa más delgada 81.8
máximo 64k Sin conexión Tabla más delgada (7,0 GB) 51,5
Tabla más ancha (7,1 GB) 58,5
En línea Mesa más delgada 136.5
Mesa más ancha 152.6
Recrear Mesa más delgada 226.5
máximo 256k Sin conexión Mesa más delgada (25,8 GB) 60,9
Tabla más ancha (25,9 GB) 61.3
En línea Mesa más delgada 149.1
Mesa más ancha 197.1
Recrear Mesa más delgada 1576,7

Esta es una sacudida injusta en el método de Andy porque, en el mundo real, no estaría realizando toda la operación de una sola vez. No mostré el uso del registro de transacciones aquí por brevedad, pero también sería más fácil controlarlo mediante el procesamiento por lotes en una operación en paralelo. Si bien su enfoque requiere más trabajo por adelantado, es mucho más seguro en términos de tiempo de inactividad y/o bloqueo. Pero puede ver en los casos en los que tiene una gran cantidad de datos fuera de línea y puede permitirse una breve interrupción, que alterar la columna directamente es mucho menos doloroso. "Demasiado grande para cambiar en el lugar" es subjetivo y puede producir diferentes resultados dependiendo de lo que signifique "grande". Antes de comprometerse con un enfoque, podría tener sentido probar el cambio con una copia razonable, ya que la operación en el lugar podría representar una compensación aceptable.

Conclusión

No escribí esto para discutir con Andy. El enfoque en la publicación original es sólido, 100% confiable y lo usamos todo el tiempo. Sin embargo, cuando se valora la fuerza bruta sobre la precisión quirúrgica, y especialmente si puede tomar una parte del tiempo de inactividad, puede haber valor en el enfoque más simple para ciertas formas de mesa.