web-dev-qa-db-fra.com

Colonne de changement rapide NVARCHAR (4000) à NVARCHAR (260)

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.

12
Nick Whaley

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:

  1. CAST la colonne comme NVARCHAR (260) dans tous les codes qui l'utilisent. L'optimiseur de requêtes calculera l'allocation de mémoire en utilisant le type de données castées au lieu du type brut.
  2. Renommez la table et créez une vue qui effectue la conversion à la place. Cela accomplit la même chose que l'option 1 mais peut limiter la quantité de code que vous devez mettre à jour.
  3. Créez une colonne calculée non persistante avec le bon type de données et faites en sorte que toutes vos requêtes soient sélectionnées dans cette colonne au lieu de celle d'origine.
  4. Renommez la colonne existante et ajoutez la colonne calculée avec le nom d'origine. Ajustez ensuite toutes vos requêtes en mettant à jour ou en insérant la colonne d'origine pour utiliser à la place le nouveau nom de colonne.
16
Joe Obbish

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.

Réécrire la table

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

Screenshot of data portion of dbcc page before and after

À 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:

Screenshot of column metadata portions of dbcc page before and after

Utilisation de la taille de table 2x dans l'espace de journal

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:

Par défaut (hors ligne) ALTER

  • Écriture du fichier de données/octets écrits: 34,809/2,193,801,216
  • Écriture du fichier journal/octets écrits: 40,953/1,484,910,080

En ligne ALTER

  • Écriture du fichier de données/octets écrits: 36 874/1 693 745 152 (baisse de 22,8%)
  • Écrits du fichier journal/octets écrits: 24 680/866 166 272 (baisse de 41%)

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.

15
Josh Darnell

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.

  1. 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)
    );
    
  2. 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
    
  3. Renommez la table "cassée" table en autre chose:

    EXEC sp_rename 'table', 'old_table';  
    
  4. Renommez la table "fixe" new_table En table:

    EXEC sp_rename 'new_table', 'table';  
    
  5. Si tout va bien, supprimez le tableau renommé "cassé":

     DROP TABLE [old_table]
     GO
    

Voilà.

Répondre à vos questions

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

1
John aka hot2use