Ran*_*der 26 sql-server sql-server-2008-r2 alter-table database-internals
我们有一个包含 2.3B 行的表。我们想将一列从 NOT NULL 更改为 NULL。该列包含在一个索引中(不是聚集索引或 PK 索引)。数据类型没有改变(它是一个 INT)。只是可空性。声明如下:
Alter Table dbo.Workflow Alter Column LineId Int NULL
Run Code Online (Sandbox Code Playgroud)
该操作在我们停止之前需要超过 10 次(我们甚至还没有让它运行完成,因为它是一个阻塞操作并且花费的时间太长)。我们可能会将表复制到开发服务器以测试实际需要多长时间。但是,我很好奇是否有人知道 SQL Server 在从 NOT NULL 转换为 NULL 时在幕后做了什么?此外,受影响的索引是否需要重建?生成的查询计划并不表明发生了什么。
有问题的表是集群的(不是堆)。
Mar*_*ith 28
正如@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
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
回来
+-----------------+--------------------+-------------+----------------+
| 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 |
+-----------------+--------------------+-------------+----------------+
Run Code Online (Sandbox Code Playgroud)
再次检查索引叶,行现在看起来像

以及上层页面中的行如下。

每行都已更新,现在包含用于列计数的两个字节以及用于 NULL_BITMAP 的另一个字节。
由于额外的行宽,非聚集索引现在有 285 个叶页和两个中间级页以及根页。
的执行计划
ALTER TABLE Foo ALTER COLUMN B CHAR(1) NULL;
Run Code Online (Sandbox Code Playgroud)
看起来如下

这会创建一个全新的索引副本,而不是更新现有副本并需要拆分页面。
它肯定会重新创建非聚集索引,而不仅仅是更新元数据。这是在 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);
Run Code Online (Sandbox Code Playgroud)
现在是有趣的部分:
DBCC IND (0, z, -1)
Run Code Online (Sandbox Code Playgroud)
这将为我们提供存储表和非聚集索引的数据库页面。
找到PagePIDwhere IndexIDis 2 和PageTypeis 2,然后执行以下操作:
DBCC TRACEON(3604) --are you sure that you are allowed to do this?
Run Code Online (Sandbox Code Playgroud)
进而:
dbcc page (0, 1, PagePID, 3) with tableresults
Run Code Online (Sandbox Code Playgroud)
请注意,标题中有一个空位图:
现在让我们做:
alter table z alter Column b int null;
Run Code Online (Sandbox Code Playgroud)
如果您实在不耐烦,您可以尝试dbcc page再次运行该命令,但它会失败,因此让我们再次使用DBCC IND (0, z, -1). 页面会像变魔术一样移动。
因此,更改列的可空性将影响覆盖该列的非聚集索引的存储,因为元数据需要更新,之后您不需要重建索引。
从 SQL Server 2016 开始ALTER TABLE ... ALTER COLUMN ...可以执行许多操作ONLINE,但是:
- 改变从一列
NOT NULL到NULL时变更列由非聚簇索引引用不支持作为一个在线操作。
| 归档时间: |
|
| 查看次数: |
37739 次 |
| 最近记录: |