Швидкий змінний стовпчик NVARCHAR (4000) до NVARCHAR (260)


12

У мене проблема з продуктивністю з дуже великими грантами пам’яті для обробки цієї таблиці з парою NVARCHAR(4000)стовпців. Справа в тому, що ці стовпці ніколи не перевищують NVARCHAR(260).

Використання

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

призводить до того, що SQL Server переписав всю таблицю (і використовував 2x розмір таблиці в просторі журналу), який складає мільярди рядків, тільки щоб нічого не змінити, не є варіантом. Збільшення ширини стовпця не має такої проблеми, але зменшення -.

Я спробував створити обмеження CHECK (DATALENGTH([col]) <= 520)або CHECK (LEN([col]) <= 260)SQL Server все ще вирішив переписати всю таблицю.

Чи є спосіб змінити тип даних стовпця як операцію, що стосується лише метаданих? Без витрат на переписування всієї таблиці? Я використовую SQL Server 2017 (14.0.2027.2 та 14.0.3192.2).

Ось зразок таблиці DDL, який слід використовувати для відтворення:

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

А потім запустіть ALTER.

Відповіді:


16

Я не знаю способу безпосередньо здійснити те, що ви шукаєте тут. Зауважте, що оптимізатор запитів наразі недостатньо розумний, щоб враховувати обмеження для обчислення гранту пам'яті, тому обмеження не допомогло б у будь-якому випадку. Кілька методів, які уникають перезапису даних таблиці:

  1. КАСТУЙТЕ стовпець як NVARCHAR (260) у всіх кодах, які його використовують. Оптимізатор запитів буде обчислювати надання пам'яті, використовуючи тип кастируемих даних замість необробленого.
  2. Перейменуйте таблицю та створіть подання, яке замість цього роблять. Це виконує те саме, що і варіант 1, але може обмежити кількість коду, який потрібно оновити.
  3. Створіть непостійний обчислюваний стовпець з правильним типом даних і виберіть усі ваші запити з цього стовпця замість оригінального.
  4. Перейменуйте існуючий стовпець і додайте обчислений стовпець з оригінальною назвою. Потім відрегулюйте всі ваші запити, вносячи оновлення чи вставки до початкового стовпця, щоб замість цього використовувати нове ім’я стовпця.

15

Чи є спосіб змінити тип даних стовпця як операцію, що стосується лише метаданих?

Я не думаю, що цей продукт працює зараз. Існує кілька дійсно чудових вирішень цього обмеження, запропонованого у відповіді Джо .

... призводить до того, що SQL Server переписав всю таблицю (та використовував 2x розмір таблиці в просторі журналу)

Я збираюся відповісти на дві частини цієї заяви окремо.

Переписування таблиці

Як я вже згадував, насправді немає жодного способу цього уникнути. Це здається реальністю ситуації, навіть якщо це не має повного сенсу з нашої точки зору як клієнтів.

Якщо дивитися на DBCC PAGEзміну стовпця з 4000 на 260, видно, що всі дані дублюються на сторінці даних (у моїй тестовій таблиці було 'A'260 разів підряд):

Скріншот частини даних сторінки dbcc до і після

На даний момент на сторінці є дві копії абсолютно однакових даних. "Старий" стовпчик по суті видаляється (ідентифікатор змінено з id = 2 на id = 67108865), а "нова" версія стовпця оновлюється, щоб вказувати на нове зміщення даних на сторінці:

Скріншот частини метаданих стовпців сторінки dbcc до та після

Використання 2x Розмір таблиці в просторі журналу

Додавання WITH (ONLINE = ON)до кінця ALTERоператора зменшує активність ведення журналу приблизно вдвічі , тому це одне вдосконалення, яке ви можете зробити, щоб зменшити кількість запису на диск / місце на диску.

Я використовував цей тестовий джгут, щоб спробувати його:

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

Я перевірив sys.dm_io_virtual_file_stats(DB_ID(N'248749'), DEFAULT)до і після запуску ALTERоператора, і ось які відмінності:

За замовчуванням (офлайн) ALTER

  • Запис / байт даних у файл даних: 34,809 / 2,193,801,216
  • Записані файли / байти журналу: 40,953 / 1,484,910,080

Інтернет ALTER

  • Записані файли даних / байтів: 36 874 / 1,693,745,152 (падіння на 22,8%)
  • Записані записи / байти журналу: 24,680 / 866,166,272 (падіння на 41%)

Як ви бачите, у записі файлу даних було незначне падіння, а у файлі журналу - велике падіння.


2

Я був у подібній ситуації багато разів.

Кроки:

Додайте новий стовпчик потрібної ширини

Використовуйте курсор з кількома тисячами повторень (можливо, десять-двадцять тисяч) на комісію, щоб скопіювати дані зі старого стовпця в новий стовпець

Відкиньте старий стовпець

Перейменуйте новий стовпець на ім'я старого стовпця

Тада!


3
Що робити, якщо деякі записи, які ви вже скопіювали, закінчуються оновленням або видаленням?
Джордж Палачіос

1
Дуже легко зробити один фінал update table set new_col = old_col where new_col <> old_col;перед тим, як скинути old_col.
Colin 't Hart

1
@ Colin'tHart такий підхід не буде працювати з мільйонами рядків ... транзакція стає величезною, і вона блокує ....
Jonesome Reinstate Monica

@samsmith Спочатку ви зробите те, що описано вище. Потім, перш ніж скидати початковий стовпець, якщо тим часом були оновлення вихідних даних, запустіть цю заяву оновлення. Це має стосуватися лише кількох рядків, які були змінені. Або я щось пропускаю?
Colin 't Hart

Для покриття рядків, оновлених під час процесу, намагаючись уникнути повного сканування, що where new_col <> old_colне призведе до інших застережень про фільтрування, ви можете додати тригер для перенесення цих змін у міру їхнього видалення та видалити його в кінці процесу. Все-таки потенційний показник ефективності, але багато невеликих сум протягом тривалості процесу замість одного величезного звернення в кінці, ймовірно (залежно від схеми оновлення вашого додатка для таблиці), що додає до набагато менше, ніж загальний хіт .
Девід Спіллетт

1

Ну є альтернатива залежно від наявного місця у вашій базі даних.

  1. Створити точну копію таблиці (наприклад new_table), для стовпця , в якому ви будете укорочення з за винятком того, 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. У вікні технічного обслуговування скопіюйте дані з "розбитої" таблиці ( table) у "фіксовану" таблицю ( new_table) простим 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. Перейменуйте «розбиту» таблицю tableна щось інше:

    EXEC sp_rename 'table', 'old_table';  
  4. Перейменуйте "фіксовану" таблицю new_tableв table:

    EXEC sp_rename 'new_table', 'table';  
  5. Якщо все добре, опустіть "розбиту" перейменовану таблицю:

     DROP TABLE [old_table]
     GO

Ось так.

Відповідаючи на ваші запитання

Чи є спосіб змінити тип даних стовпця як операцію, що стосується лише метаданих?

Ні. Наразі це неможливо

Без витрат на переписування всієї таблиці?

Ні
( Дивіться моє рішення та ін. )


Ваша "вставка у вибране з" призведе до великої таблиці (мільйони чи мільярди рядків) ENORMOUS транзакції, яка може зупинити БД на десятки чи сотні хвилин. (А також зробити ldf величезним і, можливо, порушеним доставкою журналу, якщо він використовується)
Jonesome Reinstate Monica
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.