Быстрый переход с колонки NVARCHAR (4000) на NVARCHAR (260)

12

У меня проблема с производительностью при очень больших разрешениях памяти, обрабатывающих эту таблицу с парой NVARCHAR(4000) столбцами. Дело в том, что эти столбцы никогда не бывают больше, чем NVARCHAR(260).

С помощью

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

в результате SQL Server переписывает всю таблицу (и использует размер таблицы в 2 раза больше в пространстве журнала), то есть миллиарды строк, только для того, чтобы ничего не менять, это не вариант. Увеличение ширины столбца не имеет этой проблемы, но уменьшение имеет.

Я пытался создать ограничение 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. CAST столбец как 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

Я был в подобной ситуации много раз.

Шаги:

Добавьте новый столб желаемой ширины

Используйте курсор с несколькими тысячами итераций (возможно, десять или двадцать тысяч) на коммит, чтобы скопировать данные из старого столбца в новый столбец

Оставить старый столбец

Переименовать новый столбец в имя старого столбца

Тада!

Jonesome Восстановить Монику
источник
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
    

Вот и ты.

Отвечая на ваши вопросы

Есть ли способ изменить тип данных столбца как операцию только для метаданных?

Нет. В настоящее время невозможно

Без затрат на переписывание всей таблицы?

Нет.
( См. Мое решение и другие. )

Джон ака hot2use
источник
Ваша «вставка в select from» приведет к большой таблице (миллионы или миллиарды строк) в ОГРОМНОЙ транзакции, которая может остановить работу БД на десятки или сотни минут. (А также сделать ldf огромным и, возможно, сломавшим доставку журналов, если он используется)
Jonesome Reinstate Monica