Изменение столбца с NOT NULL на NULL - что происходит под капотом?

25

У нас есть таблица с 2,3B строк в нем. Мы хотели бы изменить столбец с NOT NULL на NULL. Столбец содержится в одном индексе (не кластерный или PK-индекс). Тип данных не меняется (это INT). Просто обнуляемость. Утверждение следующее:

Alter Table dbo.Workflow Alter Column LineId Int NULL

Операция переваливает за 10, прежде чем мы ее остановим (мы даже еще не дали ей завершиться, потому что это операция блокировки и занимала слишком много времени). Вероятно, мы скопируем таблицу на dev-сервер, чтобы проверить, сколько времени на самом деле это займет. Но мне любопытно, если кто-нибудь знает, что SQL Server делает скрытно при конвертации из NOT NULL в NULL? Кроме того, нужно ли перестраивать затронутые индексы? Сгенерированный план запроса не указывает, что происходит.

Рассматриваемая таблица сгруппирована (не куча).

Рэнди Миндер
источник
2
Я думаю, что это должно было бы обновить нулевое растровое изображение на всех листовых страницах уровня данных. Бьюсь об заклад, с 2,3B строками было бы много страниц для работы. Я не слишком уверен в этом, хотя.
soulex
3
Может быть занято размещением нулевого растрового изображения в индексе. Растровое изображение NULL НЕ будет присутствовать в НЕКЛАСТЕРНОМ ИНДЕКСЕ, если все столбцы в определении индекса определены как NOT NULL.
soulex

Ответы:

27

Как упомянуто @Souplex в комментариях, одно из возможных объяснений может быть, если этот столбец является первым NULLдоступным столбцом в некластеризованном индексе, в котором он участвует.

Для следующей настройки

CREATE TABLE Foo
  (
     A UNIQUEIDENTIFIER NOT NULL DEFAULT NEWSEQUENTIALID() PRIMARY KEY,
     B CHAR(1) NOT NULL DEFAULT 'B'
  )

CREATE NONCLUSTERED INDEX ix
  ON Foo(B);

INSERT INTO Foo
            (B)
SELECT TOP 100000 'B'
FROM   master..spt_values v1,
       master..spt_values v2 

sys.dm_db_index_physical_stats показывает, что некластеризованный индекс ixимеет 248 листовых страниц и одну корневую страницу.

Типичная строка на листовой странице индекса выглядит так

введите описание изображения здесь

И в корневой странице

введите описание изображения здесь

Затем работает ...

CHECKPOINT;

GO

ALTER TABLE Foo ALTER COLUMN B  CHAR(1) NULL;


SELECT Operation, 
       Context,
       ROUND(SUM([Log Record Length]) / 1024.0,1) AS [Log KB],
       COUNT(*) as [OperationCount]
FROM sys.fn_dblog(NULL,NULL)
WHERE AllocUnitName = 'dbo.Foo.ix'
GROUP BY Operation, Context

возвращенный

+-----------------+--------------------+-------------+----------------+
|    Operation    |      Context       |   Log KB    | OperationCount |
+-----------------+--------------------+-------------+----------------+
| LOP_SET_BITS    | LCX_GAM            | 4.200000    |             69 |
| LOP_FORMAT_PAGE | LCX_IAM            | 0.100000    |              1 |
| LOP_SET_BITS    | LCX_IAM            | 4.200000    |             69 |
| LOP_FORMAT_PAGE | LCX_INDEX_INTERIOR | 8.700000    |              3 |
| LOP_FORMAT_PAGE | LCX_INDEX_LEAF     | 2296.200000 |            285 |
| LOP_MODIFY_ROW  | LCX_PFS            | 16.300000   |            189 |
+-----------------+--------------------+-------------+----------------+

Снова проверяя индексный лист, строки теперь выглядят как

введите описание изображения здесь

и строки на страницах верхнего уровня, как показано ниже.

введите описание изображения здесь

Каждая строка была обновлена ​​и теперь содержит два байта для счетчика столбцов вместе с другим байтом для NULL_BITMAP.

Из-за дополнительной ширины строки некластеризованный индекс теперь имеет 285 листовых страниц и теперь две страницы промежуточного уровня вместе с корневой страницей.

План выполнения для

 ALTER TABLE Foo ALTER COLUMN B  CHAR(1) NULL;

выглядит следующим образом

введите описание изображения здесь

Это создает новую копию индекса, а не обновляет существующую и не требует разделения страниц.

Мартин Смит
источник
9

Это определенно пересоздаст некластеризованный индекс, а не только обновит метаданные. Это проверено на SQL 2014 и не должно быть проверено в производственной системе:

CREATE TABLE [z](
    [a] [int] IDENTITY(1,1) NOT NULL,
    [b] [int] NOT NULL,
 CONSTRAINT [c_a] PRIMARY KEY CLUSTERED  ([a] ASC))
go
CREATE NONCLUSTERED INDEX [nc_b] on z (b asc)
GO
insert into z (b)
values (1);

А теперь самое интересное:

DBCC IND (0, z, -1)

Это даст нам страницы базы данных, где хранятся таблица и некластерный индекс.

Найдите PagePIDгде IndexID2 и PageType2, а затем выполните следующее:

DBCC TRACEON(3604) --are you sure that you are allowed to do this?

а потом:

dbcc page (0, 1, PagePID, 3) with tableresults

Обратите внимание, что в заголовке есть нулевое растровое изображение:

Извлечение заголовка страницы

Теперь давайте сделаем:

alter table z alter Column b int null;

Если вы действительно нетерпеливы, вы можете попробовать запустить dbcc pageкоманду еще раз, но она не удастся, поэтому давайте еще раз проверим распределение DBCC IND (0, z, -1). Страница переместится как по волшебству.

Таким образом, изменение обнуляемости столбца повлияет на хранение некластеризованных индексов, которые охватывают этот столбец, поскольку метаданные необходимо обновлять, и вам не нужно будет впоследствии перестраивать индексы.


Многие ALTER TABLE ... ALTER COLUMN ...операции могут быть выполнены, ONLINEначиная с SQL Server 2016, но:

ALTER TABLE (Transact-SQL)

  • Изменение столбца с NOT NULLна NULLне поддерживается как оперативная операция, когда на измененный столбец ссылаются некластеризованные индексы.
Spörri
источник