Colonna di modifica rapida da NVARCHAR (4000) a NVARCHAR (260)


12

Ho un problema di prestazioni con concessioni di memoria molto grandi che gestiscono questa tabella con un paio di NVARCHAR(4000)colonne. Il fatto è che queste colonne non sono mai più grandi di NVARCHAR(260).

utilizzando

ALTER TABLE [table] ALTER COLUMN [col] NVARCHAR(260) NULL

si traduce in SQL Server che riscrive l'intera tabella (e utilizza una dimensione di tabella 2x nello spazio di registro), che è miliardi di righe, solo per cambiare nulla, non è un'opzione. Aumentare la larghezza della colonna non ha questo problema, ma diminuendolo lo fa.

Ho provato a creare un vincolo CHECK (DATALENGTH([col]) <= 520)o CHECK (LEN([col]) <= 260)e SQL Server decide ancora di riscrivere l'intera tabella.

Esiste un modo per modificare il tipo di dati della colonna come un'operazione di soli metadati? Senza le spese di riscrittura dell'intero tavolo? Sto usando SQL Server 2017 (14.0.2027.2 e 14.0.3192.2).

Ecco una tabella DDL di esempio da utilizzare per riprodurre:

CREATE TABLE [table](
    id INT IDENTITY(1,1) NOT NULL,
    [col] NVARCHAR(4000) NULL,
    CONSTRAINT [PK_test] PRIMARY KEY CLUSTERED (id ASC)
);

E poi esegui il ALTER.

Risposte:


15

Esiste un modo per modificare il tipo di dati della colonna come un'operazione di soli metadati?

Non credo, ecco come funziona il prodotto in questo momento. Ci sono alcune soluzioni davvero eccezionali a questa limitazione proposta nella risposta di Joe .

... comporta la riscrittura di tutta la tabella in SQL Server (e l'utilizzo di 2x dimensioni della tabella nello spazio di registro)

Risponderò alle due parti di quella dichiarazione separatamente.

Riscrivere la tabella

Come ho detto prima, non c'è davvero alcun modo per evitarlo. Questa sembra essere la realtà della situazione, anche se non ha completamente senso dalla nostra prospettiva di clienti.

Guardare DBCC PAGEprima e dopo aver modificato la colonna da 4000 a 260 mostra che tutti i dati sono duplicati nella pagina dei dati (la mia tabella di test aveva 'A'260 volte nella riga):

Schermata della porzione di dati della pagina di dbcc prima e dopo

A questo punto, ci sono due copie degli stessi identici dati sulla pagina. La colonna "vecchia" viene sostanzialmente eliminata (l'id viene cambiato da id = 2 a id = 67108865) e la "nuova" versione della colonna viene aggiornata per puntare al nuovo offset dei dati nella pagina:

Schermata delle porzioni di metadati di colonna della pagina di dbcc prima e dopo

Utilizzo di 2x dimensioni tabella nello spazio registro

L'aggiunta WITH (ONLINE = ON)alla fine ALTERdell'istruzione riduce l'attività di registrazione di circa la metà , quindi questo è un miglioramento che potresti apportare per ridurre la quantità di scritture sullo spazio su disco / disco necessario.

Ho usato questo cablaggio di prova per provarlo:

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

Ho controllato sys.dm_io_virtual_file_stats(DB_ID(N'248749'), DEFAULT)prima e dopo l'esecuzione ALTERdell'istruzione e qui ci sono le differenze:

Predefinito (offline) ALTER

  • Scritture / byte di file di dati scritti: 34.809 / 2.193.801.216
  • Scritture / byte del file di registro scritti: 40.953 / 1.484.910.080

in linea ALTER

  • Scritture / byte di file di dati scritti: 36.874 / 1.693.745.152 (calo del 22,8%)
  • Scritture / byte di file di registro scritti: 24.680 / 866.166.272 (calo del 41%)

Come puoi vedere, c'è stata una leggera caduta nelle scritture dei file di dati e una forte caduta nelle scritture dei file di registro.


15

Non conosco un modo per realizzare direttamente quello che stai cercando qui. Si noti che Query Optimizer non è abbastanza intelligente in questo momento da tener conto dei vincoli per i calcoli delle concessioni di memoria, quindi il vincolo non avrebbe comunque aiutato. Alcuni metodi che evitano di riscrivere i dati della tabella:

  1. CAST la colonna come NVARCHAR (260) in tutti i codici che la utilizzano. Query Optimizer calcolerà la concessione di memoria utilizzando il tipo di dati casted anziché quello grezzo.
  2. Rinomina la tabella e crea una vista che esegue invece il cast. Ciò realizza la stessa cosa dell'opzione 1 ma può limitare la quantità di codice che è necessario aggiornare.
  3. Creare una colonna calcolata non persistente con il tipo di dati corretto e fare in modo che tutte le query vengano selezionate da quella colonna anziché da quella originale.
  4. Rinomina la colonna esistente e aggiungi la colonna calcolata con il nome originale. Quindi regola tutte le tue query apportando aggiornamenti o inserimenti alla colonna originale per utilizzare invece il nome della nuova colonna.

2

Sono stato in una situazione simile molte volte.

Passaggi:

Aggiungi una nuova colonna della larghezza desiderata

Utilizzare un cursore, con alcune migliaia di iterazioni (forse dieci o ventimila) per commit per copiare i dati dalla vecchia colonna alla nuova colonna

Rilascia la vecchia colonna

Rinomina nuova colonna con il nome della vecchia colonna

Tada!


3
Cosa succede se alcuni record che hai già copiato finiscono per essere aggiornati o eliminati?
George.Palacios,

1
È molto facile fare un'ultima finale update table set new_col = old_col where new_col <> old_col;prima di cadere old_col.
Colin 't Hart,

1
@ Colin'tHart quell'approccio non funzionerà con milioni di righe ... la transazione diventa enorme e si blocca ....
Jonesome Reinstate Monica

@samsmith Per prima cosa fai quello che descrivi sopra. Quindi, prima di eliminare la colonna originale, se nel frattempo sono stati apportati aggiornamenti ai dati originali, eseguire tale istruzione di aggiornamento. Dovrebbe interessare solo le poche righe che sono state modificate. Oppure mi sfugge qualcosa?
Colin 't Hart,

Per coprire le righe aggiornate durante il processo, cercando di evitare la scansione completa che where new_col <> old_colsenza altre clausole di filtro comporterebbe, è possibile aggiungere un trigger per trasferire queste modifiche mentre si verificano e rimuoverlo alla fine del processo. Ancora un potenziale successo di prestazioni, ma molte piccole quantità nel corso del processo invece di un enorme successo alla fine, probabilmente (a seconda del modello di aggiornamento della tua app per il tavolo) sommando a molto meno in totale rispetto a quell'unico successo enorme .
David Spillett,

1

Bene c'è un'alternativa a seconda dello spazio disponibile nel database.

  1. Crea una copia esatta della tua tabella (ad es. new_table), Ad eccezione della colonna in cui accorciamo da NVARCHAR(4000)a 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. In una finestra di manutenzione, copia i dati dalla tabella "rotta" ( table) alla tabella "fissa" ( new_table) con un semplice 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. Rinomina la tabella "rotta" tablein qualcos'altro:

    EXEC sp_rename 'table', 'old_table';  
  4. Rinominare la tabella "fixed" new_tablein table:

    EXEC sp_rename 'new_table', 'table';  
  5. Se tutto va bene, elimina la tabella rinominata "rotta":

     DROP TABLE [old_table]
     GO

Ecco qua.

Rispondere alle tue domande

Esiste un modo per modificare il tipo di dati della colonna come un'operazione di soli metadati?

No. Attualmente non è possibile

Senza le spese di riscrittura dell'intero tavolo?

No.
( Vedi la mia soluzione e altri. )


Il tuo "inserimento nella selezione da" si tradurrà, su una grande tabella (milioni o miliardi di righe) in una transazione ENORME, che potrebbe arrestare il database per decine o centinaia di minuti. (Oltre a rendere l'LDF enorme e possibilmente rompere la spedizione dei tronchi, se in uso)
Jonesome Reinstate Monica
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.