Быстрый переход с колонки 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

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

Шаги:

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

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

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

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

Тада!


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
    

Вот и ты.

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

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

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

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

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


Ваша «вставка в select from» приведет к большой таблице (миллионы или миллиарды строк) в ОГРОМНОЙ транзакции, которая может остановить работу БД на десятки или сотни минут. (А также сделать ldf огромным и, возможно, сломавшим доставку журналов, если он используется)
Jonesome Reinstate Monica
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.