将列从NOT NULL更改为NULL-到底发生了什么?


25

我们有一个包含2.3B行的表。我们想将列从NOT NULL更改为NULL。该列包含在一个索引中(而不是聚集索引或PK索引)。数据类型没有改变(它是一个INT)。只是可空性。声明如下:

Alter Table dbo.Workflow Alter Column LineId Int NULL

在停止该操作之前,该操作花费了超过10(我们甚至还没有让它运行完毕,因为这是一项阻塞操作,并且花费了太长时间)。我们可能会将表复制到开发服务器,以测试实际需要多长时间。但是,我很好奇,是否有人知道从NOT NULL转换为NULL时SQL Server在做什么?另外,是否需要重建受影响的索引?生成的查询计划不会指示正在发生的事情。

有问题的表是群集的(不是堆)。


2
我认为必须在所有叶级数据页上更新空位图。对于2.3B行,我敢打赌它将要处理很多页面。我对此不太确定。
souplex

3
可能也忙于在索引上放置一个空位图。如果将索引定义的所有列部分都定义为NOT NULL,则NULL位图将不会出现在NON-CLUSTERED INDEX中。
souplex

Answers:


27

正如@Souplex在评论中所暗示的,一个可能的解释可能是,如果此列是NULL它所参与的非聚集索引中的第一个-able列。

对于以下设置

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)

这将为我们提供存储表和非聚集索引的数据库页面。

找到2和2,在PagePID哪里,然后执行以下操作:IndexIDPageType

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)。该页面将被魔术般移动。

因此,更改列的可空性将影响覆盖该列的非聚集索引的存储,因为需要更新元数据,并且您此后不需要重建索引。


从SQL Server 2016开始ALTER TABLE ... ALTER COLUMN ...可以执行许多操作ONLINE,但是:

ALTER TABLE (Transact-SQL)

  • 改变从一列NOT NULLNULL时变更列由非聚簇索引引用不支持作为一个在线操作。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.