¿Hay alguna forma de alterar el tipo de datos de la columna como una operación de solo metadatos?
No lo creo, así es como funciona el producto en este momento. Hay algunas soluciones realmente buenas para esta limitación propuesta en la respuesta de Joe .
... da como resultado que SQL Server reescriba toda la tabla (y use un tamaño de tabla 2x en el espacio de registro)
Voy a responder a las dos partes de esa declaración por separado.
Reescribiendo la tabla
Como mencioné antes, en realidad no hay forma de evitar esto. Esa parece ser la realidad de la situación, incluso si no tiene sentido desde nuestra perspectiva como clientes.
Mirar DBCC PAGE
antes y después de cambiar la columna de 4000 a 260 muestra que todos los datos están duplicados en la página de datos (mi tabla de prueba tenía 'A'
260 veces seguidas):
En este punto, hay dos copias de los mismos datos exactos en la página. La columna "antigua" se elimina esencialmente (la identificación cambia de id = 2 a id = 67108865), y la versión "nueva" de la columna se actualiza para señalar el nuevo desplazamiento de los datos en la página:
Uso de 2x tamaño de tabla en espacio de registro
Agregar WITH (ONLINE = ON)
al final de la ALTER
declaración reduce la actividad de registro a la mitad , por lo que esta es una mejora que podría hacer para reducir la cantidad de escrituras en el disco / espacio en disco necesario.
Usé este arnés de prueba para probarlo:
USE [master];
GO
DROP DATABASE IF EXISTS [248749];
GO
CREATE DATABASE [248749]
ON PRIMARY
(
NAME = N'248749',
FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL14.SQL2017\MSSQL\DATA\248749.mdf',
SIZE = 2048000KB,
FILEGROWTH = 65536KB
)
LOG ON
(
NAME = N'248749_log',
FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL14.SQL2017\MSSQL\DATA\248749_log.ldf',
SIZE = 2048000KB,
FILEGROWTH = 65536KB
);
GO
USE [248749];
GO
CREATE TABLE dbo.[table]
(
id int IDENTITY(1,1) NOT NULL,
[col] nvarchar (4000) NULL,
CONSTRAINT [PK_test] PRIMARY KEY CLUSTERED (id ASC)
);
INSERT INTO dbo.[table]
SELECT TOP (1000000)
REPLICATE(N'A', 260)
FROM master.dbo.spt_values v1
CROSS JOIN master.dbo.spt_values v2
CROSS JOIN master.dbo.spt_values v3;
GO
Verifiqué sys.dm_io_virtual_file_stats(DB_ID(N'248749'), DEFAULT)
antes y después de ejecutar la ALTER
declaración, y aquí están las diferencias:
Predeterminado (sin conexión) ALTER
- Escrituras de archivo de datos / bytes escritos: 34,809 / 2,193,801,216
- Escrituras de archivo de registro / bytes escritos: 40,953 / 1,484,910,080
En línea ALTER
- Escrituras de archivo de datos / bytes escritos: 36,874 / 1,693,745,152 (22.8% de caída)
- Escrituras de archivo de registro / bytes escritos: 24,680 / 866,166,272 (41% de caída)
Como puede ver, hubo una ligera caída en las escrituras del archivo de datos y una caída importante en las escrituras del archivo de registro.
update table set new_col = old_col where new_col <> old_col;
antes de caerold_col
.where new_col <> old_col
sin otras cláusulas de filtrado resultará, puede agregar un disparador para llevar estos cambios a medida que suceden y eliminarlos al final del proceso. Sigue siendo un posible éxito en el rendimiento, pero muchas pequeñas cantidades a lo largo del proceso en lugar de un gran éxito al final, probablemente (dependiendo del patrón de actualización de la aplicación para la tabla) que suman mucho menos en total que ese gran éxito .Bueno, hay una alternativa dependiendo del espacio disponible en su base de datos.
Cree una copia exacta de su tabla (p
new_table
. Ej. ), Excepto la columna desde donde acortaráNVARCHAR(4000)
aNVARCHAR(260)
:En una ventana de mantenimiento, copie los datos de la tabla "rota" (
table
) a la tabla "fija" (new_table
) con un simpleINSERT ... INTO ... SELECT ....
:Cambie el nombre de la tabla "rota"
table
a otra:Cambie el nombre de la tabla "fija"
new_table
atable
:Si todo está bien, suelte la tabla renombrada "rota":
Ahí tienes.
Contestando tus preguntas
No. Actualmente no es posible
No.
( Vea mi solución y otras ) .
fuente