J'ai un problème de performances avec de très grandes allocations de mémoire pour gérer cette table avec quelques colonnes NVARCHAR(4000)
. La chose est que ces colonnes ne sont jamais plus grandes que NVARCHAR(260)
.
En utilisant
ALTER TABLE [table] ALTER COLUMN [col] NVARCHAR(260) NULL
entraîne SQL Server réécriture de la table entière (et en utilisant 2x la taille de la table dans l'espace journal), qui est des milliards de lignes, pour ne rien changer, n'est pas une option. L'augmentation de la largeur de colonne n'a pas ce problème, mais sa diminution le fait.
J'ai essayé de créer une contrainte CHECK (DATALENGTH([col]) <= 520)
ou CHECK (LEN([col]) <= 260)
et SQL Server décide toujours de réécrire la table entière.
Existe-t-il un moyen de modifier le type de données de colonne en tant qu'opération de métadonnées uniquement? Sans les frais de réécriture de la table entière? J'utilise SQL Server 2017 (14.0.2027.2 et 14.0.3192.2).
Voici un exemple de table DDL à utiliser pour reproduire:
CREATE TABLE [table](
id INT IDENTITY(1,1) NOT NULL,
[col] NVARCHAR(4000) NULL,
CONSTRAINT [PK_test] PRIMARY KEY CLUSTERED (id ASC)
);
Et puis exécutez le ALTER
.
Je ne connais pas de moyen d'accomplir directement ce que vous cherchez ici. Notez que l'optimiseur de requête n'est pas assez intelligent pour le moment pour prendre en compte les contraintes pour les calculs d'allocation de mémoire, de sorte que la contrainte n'aurait de toute façon pas aidé. Quelques méthodes qui évitent de réécrire les données de la table:
Existe-t-il un moyen de modifier le type de données de colonne en tant qu'opération de métadonnées uniquement?
Je ne pense pas, c'est ainsi que fonctionne le produit en ce moment. Il existe de très bonnes solutions de contournement à cette limitation proposée dans Joe's answer .
... entraîne la réécriture de SQL Server de la table entière (et l'utilisation d'une taille de table 2x dans l'espace journal)
Je vais répondre séparément aux deux parties de cette déclaration.
Comme je l'ai mentionné précédemment, il n'y a vraiment aucun moyen d'éviter cela. Cela semble être la réalité de la situation, même si cela n'a pas de sens de notre point de vue en tant que clients.
L'examen de DBCC PAGE
Avant et après le changement de la colonne de 4000 à 260 montre que toutes les données sont dupliquées sur la page de données (ma table de test avait 'A'
260 fois de suite):
À ce stade, il existe deux copies des mêmes données exactes sur la page. La "vieille" colonne est essentiellement supprimée (l'ID est changé de id = 2 à id = 67108865), et la "nouvelle" version de la colonne est mise à jour pour pointer vers le nouveau décalage des données sur la page:
L'ajout de WITH (ONLINE = ON)
à la fin de l'instruction ALTER
réduit l'activité de consignation d'environ la moitié , c'est donc un amélioration que vous pourriez apporter pour réduire la quantité d'écritures sur le disque/l'espace disque nécessaire.
J'ai utilisé ce harnais de test pour l'essayer:
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
J'ai vérifié sys.dm_io_virtual_file_stats(DB_ID(N'248749'), DEFAULT)
avant et après avoir exécuté l'instruction ALTER
, et voici les différences:
ALTER
ALTER
Comme vous pouvez le voir, il y a eu une légère baisse des écritures du fichier de données et une baisse importante des écritures du fichier journal.
J'ai été dans une situation similaire à plusieurs reprises.
Pas :
Ajouter un nouveau col de largeur souhaitée
Utilisez un curseur, avec quelques milliers d'itérations (peut-être dix ou vingt mille) par commit pour copier les données de l'ancienne colonne vers la nouvelle colonne
Supprimer l'ancienne colonne
Renommer la nouvelle colonne en nom de l'ancienne colonne
Tada!
Eh bien, il existe une alternative en fonction de l'espace disponible dans votre base de données.
Créez une copie exacte de votre table (par exemple new_table
), À l'exception de la colonne où vous raccourcirez de NVARCHAR(4000)
à NVARCHAR(260)
:
CREATE TABLE [new_table](
id INT IDENTITY(1,1) NOT NULL,
[col] NVARCHAR(260) NULL,
CONSTRAINT [PK_test_new] PRIMARY KEY CLUSTERED (id ASC)
);
Dans une fenêtre de maintenance, copiez les données de la table "cassée" (table
) dans la table "fixe" (new_table
) Avec un simple INSERT ... INTO ... SELECT ....
:
SET IDENTITY_INSERT [new_table] ON
GO
INSERT id, col INTO [new_table] SELECT id, col from [table]
GO
SET IDENTITY_INSERT [new_table] OFF
GO
Renommez la table "cassée" table
en autre chose:
EXEC sp_rename 'table', 'old_table';
Renommez la table "fixe" new_table
En table
:
EXEC sp_rename 'new_table', 'table';
Si tout va bien, supprimez le tableau renommé "cassé":
DROP TABLE [old_table]
GO
Voilà.
Existe-t-il un moyen de modifier le type de données de colonne en tant qu'opération de métadonnées uniquement?
Non actuellement impossible
Sans les frais de réécriture de la table entière?
Non.
(Voir ma solution et autres.)