列をNOT NULLからNULLに変更する-内部で何が起こっているのでしょうか?


25

2.3B行のテーブルがあります。列をNOT NULLからNULLに変更したいと思います。列は1つのインデックスに含まれます(クラスター化インデックスまたはPKインデックスではありません)。データ型は変更されていません(INTです)。ただnullability。ステートメントは次のとおりです。

Alter Table dbo.Workflow Alter Column LineId Int NULL

操作は、停止する前に10を超えます(ブロッキング操作であり、時間がかかりすぎたため、完了まで実行することすらまだできていません)。テーブルを開発サーバーにコピーして、実際にかかる時間をテストします。しかし、NOT NULLからNULLに変換する際にSQL Serverが内部で何をしているのかを誰かが知っているのか興味がありますか?また、影響を受けるインデックスを再構築する必要がありますか?生成されたクエリプランは、何が起こっているのかを示していません。

問題のテーブルはクラスター化されています(ヒープではありません)。


2
すべてのリーフレベルデータページでnullビットマップを更新する必要があると思います。また、2.3B行では、処理するページがたくさんあるはずです。しかし、これについてはあまりよくわかりません。
souplex

3
インデックスにnullビットマップを配置するのに忙しい可能性があります。インデックス定義のすべての列部分がNOT NULLとして定義されている場合、NON-CLUSTERED INDEXにはNULLビットマップは存在しません。
souplex

回答:


27

コメントで@Souplexが示唆したように、考えられる説明の1つは、この列が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のリーフページと1つのルートページがあることを示しています。

インデックスリーフページの典型的な行は次のようになります

ここに画像の説明を入力してください

そしてルートページで

ここに画像の説明を入力してください

その後、実行中...

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 |
+-----------------+--------------------+-------------+----------------+

インデックスリーフを再度確認すると、行は次のようになります。

ここに画像の説明を入力してください

上位ページの行は次のとおりです。

ここに画像の説明を入力してください

各行が更新され、列カウント用の2バイトとNULL_BITMAP用の別のバイトが含まれるようになりました。

追加の行幅により、非クラスター化インデックスには285のリーフページがあり、ルートページと共に2つの中間レベルページがあります。

の実行計画

 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

ヘッダーにnullビットマップがあることに注意してください。

ページヘッダー抽出

やってみましょう:

alter table z alter Column b int null;

本当に短気な場合は、dbcc pageコマンドをもう一度実行してみてください。失敗しDBCC IND (0, z, -1)ます。そのため、でもう一度割り当てを確認しましょう。ページは魔法のように移動します。

そのため、メタデータを更新する必要があり、後でインデックスを再構築する必要がないため、カラムのNULL可能性を変更すると、そのカラムをカバーする非クラスター化インデックスのストレージに影響します。


SQL Server 2016以降、多くのALTER TABLE ... ALTER COLUMN ...操作を実行できますがONLINE、次のことが可能です。

ALTER TABLE (Transact-SQL)

  • 列をからNOT NULLNULL変更することは、変更された列が非クラスター化インデックスによって参照されている場合、オンライン操作としてサポートされていません。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.