データ用にディスク上で4.3 GBを取る64m行のテーブルがあります。
各行は、約30バイトの整数列と、テキスト用の変数NVARCHAR(255)
列です。
データ型Datetimeoffset(0)
のNULLABLE列を追加しました。
次に、すべての行に対してこの列を更新し、すべての新しい挿入がこの列に値を配置することを確認しました。
NULLエントリがなくなったら、このコマンドを実行して新しいフィールドを必須にしました。
ALTER TABLE tblCheckResult
ALTER COLUMN [dtoDateTime] [datetimeoffset](0) NOT NULL
その結果、トランザクションログのサイズが6GBから36GB以上に大幅に増加しました。
この単純なコマンドでSQL Server 2008 R2がこのような巨大な成長を遂げるために、SQL Server 2008 R2がいったい何をしているのか、だれでも知っていますか?
列をNOT NULLに変更すると、NULL値がない場合でも、SQL Serverはevery単一ページにアクセスする必要があります。 FILL FACTORによっては、実際には多くのページ分割が発生する可能性があります。もちろん、タッチされたすべてのページをログに記録する必要があります。分割が原因で、多くのページで2つの変更をログに記録する必要があるのではないかと思います。ただし、すべての処理が1つのパスで行われるため、ログはすべての変更を考慮する必要があるため、キャンセルをクリックすると、元に戻す内容を正確に把握できます。
例。シンプルなテーブル:
DROP TABLE dbo.floob;
GO
CREATE TABLE dbo.floob
(
id INT IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
bar INT NULL
);
INSERT dbo.floob(bar) SELECT NULL UNION ALL SELECT 4 UNION ALL SELECT NULL;
ALTER TABLE dbo.floob ADD CONSTRAINT df DEFAULT(0) FOR bar
それでは、ページの詳細を見てみましょう。最初に、処理しているページとDB_IDを見つける必要があります。私の場合、foo
というデータベースを作成しましたが、DB_IDはたまたま5でした。
DBCC TRACEON(3604, -1);
DBCC IND('foo', 'dbo.floob', 1);
SELECT DB_ID();
出力は、ページ159(DBCC IND
を含むPageType = 1
出力の唯一の行)に関心があることを示していました。
ここで、OPのシナリオを進めながら、選択ページの詳細をいくつか見てみましょう。
DBCC PAGE(5, 1, 159, 3);
UPDATE dbo.floob SET bar = 0 WHERE bar IS NULL;
DBCC PAGE(5, 1, 159, 3);
ALTER TABLE dbo.floob ALTER COLUMN bar INT NOT NULL;
DBCC PAGE(5, 1, 159, 3);
私は深い内面の人間ではないので、今、私はこれに対するすべての答えを持っているわけではありません。しかし、更新操作とNOT NULL制約の追加の両方が紛れもなくページへの書き込みであるのに対して、後者はまったく異なる方法でそれを行うのは明らかです。 null可能列をnull可能ではない列に交換することで、ビットをいじるだけでなく、実際にレコードの構造を変更するようです。なぜそれをしなければならないのか、私にはよくわかりません- ストレージエンジンチーム への良い質問だと思います。 SQL Server 2012はこれらのシナリオのいくつかをFWIWではるかにうまく処理すると信じていますが、まだ徹底的なテストは行っていません。
コマンドを実行するとき
_ALTER COLUMN ... NOT NULL
_
これは、列の追加、更新、列の削除操作として実装されているようです。
sys.sysrscols
_に挿入されます。 _128
_のstatus
ビットが設定され、列がNULL
sを許可しないことを示しますsys.sysrscols
_のメタデータのみの変更です。rscolid
が大きな整数に更新され、status
ビット2がオンになって削除されたことを示します)sys.sysrscols
_のエントリは、古い列のrscolid
を与えるように変更されます。大量のロギングを引き起こす可能性がある可能性がある操作は、テーブル内のすべての行のUPDATE
ですが、これが常にが発生します。行の「変更前」と「変更後」の画像が同一の場合、これは 非更新更新 として扱われ、これまでのテストではログに記録されません。
したがって、大量のログを取得する理由に関する説明は、行の「前」と「後」のバージョンが正確に同じではない理由によって異なります。
FixedVar
形式で保存された可変長列の場合、_NOT NULL
_に設定すると、常にログに記録する必要のある行が変更されることがわかりました。列数と可変長列数の両方が増分され、データを複製する可変長セクションの最後に新しい列が追加されます。
datetimeoffset(0)
は固定長ですが、FixedVar
形式で保存された固定長列の場合、古い列と新しい列の両方に、行の固定長データ部分で同じスロットが与えられているように見えます。それらは両方とも同じ長さと値を持ち、行の「前」と「後」のバージョンは同じです。これは@Aaronの回答で見ることができます。 _ALTER TABLE dbo.floob ALTER COLUMN bar INT NOT NULL;
_の前後の行の両方のバージョンは
_0x10000c00 01000000 00000000 020000
_
これはログに記録されません。
私のイベントの説明から論理的には、列数_02
_を_03
_に増やす必要があるため、実際には行はここで異なるはずですが、実際にはそのような変更は発生しません。
これが固定長列で発生する理由として考えられる理由は、次のとおりです。
SPARSE
として宣言された場合、新しい列は元の行とは異なる行に格納され、前と後の行のイメージが異なります。ALTER TABLE
_操作がいくつかある可能性があります。たとえば、新しいnull可能な可変長列が追加された場合、これはもともとメタデータのみの変更として適用され、次に更新されたときに実際に行に書き出されるだけです(この最後のインスタンスで実際に行われる書き込みは、列カウントセクションと、行の最後にあるNULL
varchar
列としての_NULL_BITMAP
_はスペースを取らない)200.000.000行のテーブルについても同じ問題に直面しました。最初にnullableの列を追加してから、すべての行を更新し、最後にNOT NULL
ステートメントを使用して列をALTER TABLE ALTER COLUMN
に変更しました。これにより、2つの巨大なトランザクションがログファイルを驚異的に爆破しました(170 GBの増加)。
私が見つけた最速の方法は次のとおりです:
デフォルト値を使用して列を追加します
ALTER TABLE table1 ADD column1 INT NOT NULL DEFAULT (1)
動的SQLを使用してデフォルトの制約を削除します。これは、以前に制約に名前が付けられていなかったためです。
DECLARE
@constraint_name SYSNAME,
@stmt NVARCHAR(510);
SELECT @CONSTRAINT_NAME = DC.NAME
FROM SYS.DEFAULT_CONSTRAINTS DC
INNER JOIN SYS.COLUMNS C
ON DC.PARENT_OBJECT_ID = C.OBJECT_ID
AND DC.PARENT_COLUMN_ID = C.COLUMN_ID
WHERE
PARENT_OBJECT_ID = OBJECT_ID('table1')
AND C.NAME = 'column1';
実行時間は30分以上から10分に短縮されました。これには、トランザクションレプリケーションによる変更の複製も含まれます。 SQL Server 2008のインストール(SP2)を実行しています。
次のテストを実行しました。
create table tblCheckResult(
ColID int identity
, dtoDateTime Datetimeoffset(0) null
)
go
insert into tblCheckResult (dtoDateTime)
select getdate()
go 10000
checkpoint
ALTER TABLE tblCheckResult
ALTER COLUMN [dtoDateTime] [datetimeoffset](0) NOT NULL
select * from fn_dblog(null,null)
これは、トランザクションをロールバックする場合に備えて、ログが保持する予約済みスペースに関係していると思います。 LOP_BEGIN_XACT行の「ログ予約」列にあるfn_dblog関数を見て、予約しようとしているスペースの量を確認します。