Как добавить столбец NOT NULL в большую таблицу в SQL Server? - PullRequest
50 голосов
/ 13 ноября 2008

Чтобы добавить столбец NOT NULL в таблицу с большим количеством записей, необходимо применить ограничение DEFAULT. Это ограничение заставляет всю команду ALTER TABLE выполняться долго, если таблица очень большая. Это потому что:

Предположения:

  1. Ограничение DEFAULT изменяет существующие записи. Это означает, что БД необходимо увеличить размер каждой записи, что заставляет его переносить записи на полных страницах данных на другие страницы данных, и это требует времени.
  2. Обновление DEFAULT выполняется как атомарная транзакция. Это означает, что необходимо увеличить журнал транзакций, чтобы при необходимости можно было выполнить откат.
  3. Журнал транзакций отслеживает всю запись. Следовательно, даже если изменено только одно поле, пространство, необходимое журналу, будет основано на размере всей записи, умноженном на количество существующих записей. Это означает, что добавление столбца в таблицу с небольшими записями будет быстрее, чем добавление столбца в таблицу с большими записями, даже если общее число записей одинаково для обеих таблиц.

Возможные решения:

  1. Смиритесь с этим и дождитесь завершения процесса. Просто убедитесь, что период ожидания очень длинный. Проблема в том, что это может занять несколько часов или дней в зависимости от количества записей.
  2. Добавить столбец, но разрешить NULL. После этого выполните запрос UPDATE, чтобы установить значение DEFAULT для существующих строк. Не делайте ОБНОВЛЕНИЕ *. Обновляйте пакеты записей одновременно, иначе вы столкнетесь с той же проблемой, что и решение № 1. Проблема с этим подходом состоит в том, что вы получаете столбец, который допускает NULL, когда вы знаете, что это ненужный параметр. Я полагаю, что в некоторых документах о наилучшей практике говорится, что у вас не должно быть столбцов, которые допускают NULL, если в этом нет необходимости.
  3. Создать новую таблицу с той же схемой. Добавьте столбец к этой схеме. Перенесите данные из исходной таблицы. Удалите исходную таблицу и переименуйте новую таблицу. Я не уверен, насколько это лучше, чем # 1.

Вопросы:

  1. Верны ли мои предположения?
  2. Это мои единственные решения? Если так, какой из них лучший? Я не знаю, что еще я мог сделать?

Ответы [ 12 ]

59 голосов
/ 20 июля 2009

Я столкнулся с этой проблемой и для моей работы. И мое решение заключается в # 2.

Вот мои шаги (я использую SQL Server 2005):

1) Добавить столбец в таблицу со значением по умолчанию:

ALTER TABLE MyTable ADD MyColumn varchar(40) DEFAULT('')

2) Добавьте ограничение NOT NULL с параметром NOCHECK. NOCHECK не применяется к существующим значениям:

ALTER TABLE MyTable WITH NOCHECK
ADD CONSTRAINT MyColumn_NOTNULL CHECK (MyColumn IS NOT NULL)

3) Обновлять значения пошагово в таблице:

GO
UPDATE TOP(3000) MyTable SET MyColumn = '' WHERE MyColumn IS NULL
GO 1000
  • Оператор обновления будет обновлять только максимум 3000 записей. Это позволяет сохранить часть данных в то время. Я должен использовать «MyColumn IS NULL», потому что в моей таблице нет первичного ключа последовательности.

  • GO 1000 выполнит предыдущий оператор 1000 раз. Это обновит 3 миллиона записей, если вам нужно больше, просто увеличьте это число. Он будет продолжать выполняться до тех пор, пока SQL Server не вернет 0 записей для оператора UPDATE.

3 голосов
/ 13 ноября 2008

Вот что я бы попробовал:

  • Сделать полную резервную копию базы данных.
  • Добавить новый столбец с пустыми значениями - не устанавливать значение по умолчанию.
  • Установить ПРОСТОЕ восстановление, которое усекает журнал транзакций, как только каждый пакет фиксируется.
  • SQL: ALTER DATABASE XXX УСТАНОВИТЬ ПРОСТАЯ ВОССТАНОВЛЕНИЕ
  • Запускайте обновление партиями, как вы обсуждали выше, фиксируя после каждого.
  • Сброс нового столбца, чтобы больше не разрешать нули.
  • Вернитесь к нормальному ПОЛНОМУ восстановлению.
  • SQL: ALTER DATABASE XXX УСТАНОВИТЬ ВОССТАНОВЛЕНИЕ ПОЛНОЙ
  • Снова сделайте резервную копию базы данных.

Использование модели восстановления SIMPLE не останавливает ведение журнала, но значительно снижает его влияние. Это связано с тем, что сервер сбрасывает информацию о восстановлении после каждой фиксации.

2 голосов
/ 07 января 2013

Просто для обновления этой информации.

В SQL Server 2012 это теперь можно выполнять как оперативную операцию при следующих обстоятельствах

  1. Только для Enterprise Edition
  2. По умолчанию должна быть постоянная времени выполнения

Для примеров второго требования может быть литеральная константа или функция, такая как GETDATE(), которая оценивает одно и то же значение для всех строк. Значение по умолчанию NEWID() будет не квалифицироваться и все равно закончится обновлением всех строк тут же.

Для значений по умолчанию, которые квалифицируют SQL Server, оценивает их и сохраняет результат в качестве значения по умолчанию в метаданных столбца, поэтому он не зависит от создаваемого ограничения по умолчанию (которое может даже быть удалено, если больше не требуется). Это можно просмотреть в sys.system_internals_partition_columns. Значение не записывается в строки, пока в следующий раз они не будут обновлены.

Подробнее об этом здесь: онлайн ненулевое значение со столбцом значений add в sql server 2012

2 голосов
/ 14 ноября 2008

Вы могли бы:

  1. Начать транзакцию.
  2. Возьмите блокировку записи на исходной таблице, чтобы никто не писал на нее.
  3. Создать теневую таблицу с новой схемой.
  4. Перенос всех данных из исходной таблицы.
  5. выполнить sp_rename , чтобы переименовать старую таблицу.
  6. выполнить sp_rename , чтобы переименовать новую таблицу в.
  7. Наконец, вы совершаете транзакцию.

Преимущество этого подхода состоит в том, что ваши читатели смогут получить доступ к таблице в течение длительного процесса, и что вы можете выполнять любые изменения схемы в фоновом режиме.

0 голосов
/ 11 ноября 2016

1) Добавьте столбец в таблицу со значением по умолчанию:

ALTER TABLE MyTable ADD MyColumn int default 0

2) Обновлять значения постепенно в таблице (тот же эффект, что и принятый ответ). Настройте количество записей, которые обновляются в вашей среде, чтобы избежать блокировки других пользователей / процессов.

declare @rowcount int = 1

while (@rowcount > 0)
begin           

    UPDATE TOP(10000) MyTable SET MyColumn = 0 WHERE MyColumn IS NULL       
    set @rowcount = @@ROWCOUNT

end

3) Измените определение столбца, чтобы оно не было нулевым. Выполните следующее в тот момент, когда таблица не используется (или запланируйте несколько минут простоя). Я успешно использовал это для таблиц с миллионами записей.

ALTER TABLE MyTable ALTER COLUMN MyColumn int NOT NULL
0 голосов
/ 25 мая 2015

признал, что это старый вопрос. Мой коллега недавно сказал мне, что он может сделать это с помощью одного оператора alter table в таблице с 13,6M строками. В SQL Server 2012 он завершился за секунду. Мне удалось подтвердить то же самое в таблице с 8M строками. Что-то изменилось в более поздней версии SQL Server?

Alter table mytable add mycolumn char(1) not null default('N');
0 голосов
/ 02 октября 2013

У меня была похожая проблема, и я пошел с измененным подходом # 3. В моем случае база данных находилась в режиме восстановления SIMPLE, и на таблицу, в которую предполагалось добавить столбец, не было никаких ограничений FK.

Вместо создания новой таблицы с той же схемой и копирования содержимого исходной таблицы я использовал синтаксис SELECT… INTO .

По данным Microsoft (http://technet.microsoft.com/en-us/library/ms188029(v=sql.105).aspx)

Количество журналов для SELECT ... INTO зависит от модели восстановления действует для базы данных. По простой модели восстановления или модель восстановления с массовой регистрацией, минимальная регистрация операций с массовыми операциями. С минимальное ведение журнала, с помощью оператора SELECT… INTO может быть больше эффективнее, чем создание таблицы, а затем заполнение таблицы Вставить заявление. Для получения дополнительной информации см. Операции, которые могут быть Минимально зарегистрировано.

Последовательность шагов:

1.Перемещение данных из старой таблицы в новую при добавлении нового столбца со значением по умолчанию

 SELECT  table.*,   cast (‘default’ as nvarchar(256)) new_column
 INTO    table_copy 
 FROM    table

2. Удалить старый стол

 DROP TABLE  table

3. Переименовать вновь созданную таблицу

 EXEC sp_rename 'table_copy',  ‘table’

4.Создать необходимые ограничения и индексы для новой таблицы

В моем случае в таблице было более 100 миллионов строк, и этот подход завершился быстрее, чем в подходе №2, а рост пространства журнала был минимальным.

0 голосов
/ 16 марта 2009

У меня была похожая проблема, и я выбрал ваш вариант №2. Это займет 20 минут, а не 32 часа !!! Огромная разница, спасибо за отзыв. Я написал полную запись в блоге об этом, но вот важный sql:

Alter table MyTable
Add MyNewColumn char(10) null default '?';
go

update MyTable set MyNewColumn='?' where MyPrimaryKey between 0 and 1000000
go
update MyTable set MyNewColumn='?' where MyPrimaryKey between 1000000 and 2000000
go
update MyTable set MyNewColumn='?' where MyPrimaryKey between 2000000 and 3000000
go
..etc..

Alter table MyTable
Alter column MyNewColumn char(10) not null;

И запись в блоге, если вам интересно: http://splinter.com.au/adding-a-column-to-a-massive-sql-server-table

0 голосов
/ 14 ноября 2008

Если вы хотите столбец в той же таблице, вам просто нужно это сделать. Теперь вариант 3 является потенциально лучшим для этого, потому что вы все еще можете иметь базу данных «живой», пока эта операция продолжается. Если вы используете опцию 1, таблица блокируется во время выполнения операции, и тогда вы действительно застряли.

Если вам действительно все равно, находится ли столбец в таблице, то я полагаю, что сегментированный подход является следующим лучшим. Тем не менее, я действительно стараюсь избегать этого (до такой степени, что я этого не делаю), потому что тогда, как говорит Чарльз Бретана, вам нужно будет убедиться и найти все места, которые обновляют / вставляют эту таблицу и модифицируют их. Тьфу!

0 голосов
/ 14 ноября 2008

Я бы использовал КУРСОР вместо ОБНОВЛЕНИЯ. Курсор обновит все соответствующие записи в пакете, запись за записью - это занимает время, но не блокирует таблицу.

Если вы хотите избежать блокировок, используйте WAIT.

Также я не уверен, что ограничение DEFAULT изменяет существующие строки. Вероятно, использование ограничения NOT NULL вместе с DEFAULT вызывает случай, описанный автором.

Если он изменится, добавь его в конце Таким образом, псевдокод будет выглядеть так:

-- without NOT NULL constrain -- we will add it in the end
ALTER TABLE table ADD new_column INT DEFAULT 0

DECLARE fillNullColumn CURSOR LOCAL FAST_FORWARD
    SELECT 
        key
    FROM
        table WITH (NOLOCK)
    WHERE
        new_column IS NULL

OPEN fillNullColumn

DECLARE 
    @key INT

FETCH NEXT FROM fillNullColumn INTO @key

WHILE @@FETCH_STATUS = 0 BEGIN
     UPDATE
         table WITH (ROWLOCK)
     SET
         new_column = 0 -- default value
     WHERE
         key = @key

     WAIT 00:00:05 --wait 5 seconds, keep in mind it causes updating only 12 rows per minute

     FETCH NEXT FROM fillNullColumn INTO @key
END

CLOSE fillNullColumn
DEALLOCATE fillNullColumn

ALTER TABLE table ALTER COLUMN new_column ADD CONSTRAIN xxx

Я уверен, что есть некоторые синтаксические ошибки, но я надеюсь, что это помогите решить вашу проблему.

Удачи!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...