昨年、AndyMallonはint
からの列のアップサイズについてブログを書きました bigint
へ ダウンタイムなし。 (SQL Serverの最新バージョンでこれがメタデータのみの操作ではない理由は私にはわかりませんが、それは別の投稿です。)
通常、この問題に対処する場合、それらは幅が広く巨大なテーブル(行数とサイズの両方)であり、変更する必要がある列は、クラスタリングキーの唯一の/先頭の列です。通常、他にも複雑な問題があります。インバウンド外部キーの制約、多くの非クラスター化インデックス、ログアクティビティに非常に敏感なビジーデータベース(変更の追跡、レプリケーション、可用性グループ、または3つすべてに関係しているため) )。
このため、Andyの概要のようなアプローチを採用する必要があります。このアプローチでは、新しいスキーマを使用してシャドウテーブルを作成し、両方のコピーの同期を維持するトリガーを作成してから、交換の準備ができるまで、そのチームのペースでバッチ/バックフィルします。実際の取引としてコピーで。
でも私は怠け者です!
ダウンタイム/ブロッキングの小さなウィンドウがあれば、列を直接変更できる場合があり、それははるかに簡単な操作になります。先週、そのようなケースが1つ発生し、テーブルは1TBを超えましたが、行は10万行しかありませんでした。ほとんどすべてのデータはオフロー(LOB)であり、必要に応じてダウンタイムの小さなウィンドウを提供でき、変更の追跡を無効にして、とにかく再構成することを計画していました。クラスタ化されたPKを再作成することで、LOBデータに(多くの場合)触れる必要がないことを確信しているので、変更を直接適用できる場合である可能性があることを提案しました。
孤立したシナリオ(インバウンド外部キー、追加のインデックス、ログリーダーに依存するアクティビティ、同時実行性の懸念なし)で、いくつかのテストをまとめて、この変更が期間の観点から何を必要とするかを確認しました。トランザクションログへの影響。事前に答える方法がわからなかった主な質問は、「キー以外のデータが大量にある場合に、テーブルをインプレースで更新するための増分コストはいくらですか?」でした。
ここでは、たくさんのことを1つの投稿にまとめようと思います。私は多くのテストを行いましたが、すべてのテストシナリオが当てはまるとは限りませんが、それはあらゆる種類の関連性があります。我慢してください。
テーブル
のみのベースラインを含む6つのテーブルを作成しました キー列、4Kが行に格納された1つのテーブル、およびさまざまな量の文字列データ(4K、16K、64K、および256K)が入力されたvarchar(max)列を持つ4つのテーブルがありました。
CREATE TABLE dbo.withJustId ( id int NOT NULL, CONSTRAINT pk_withJustId PRIMARY KEY CLUSTERED (id) ); CREATE TABLE dbo.withoutLob ( id int NOT NULL, extradata varchar(4000) NOT NULL DEFAULT (REPLICATE('x', 4000)), CONSTRAINT pk_withoutLob PRIMARY KEY CLUSTERED (id) ); CREATE TABLE dbo.withLob004 ( id int NOT NULL, extradata varchar(max) NOT NULL DEFAULT (REPLICATE('x', 4000)), CONSTRAINT pk_withLob004 PRIMARY KEY CLUSTERED (id) ); CREATE TABLE dbo.withLob016 ( id int NOT NULL, extradata varchar(max) NOT NULL DEFAULT (REPLICATE(CONVERT(varchar(max),'x'), 16000)), CONSTRAINT pk_withLob016 PRIMARY KEY CLUSTERED (id) ); CREATE TABLE dbo.withLob064 ( id int NOT NULL, extradata varchar(max) NOT NULL DEFAULT (REPLICATE(CONVERT(varchar(max),'x'), 64000)), CONSTRAINT pk_withLob064 PRIMARY KEY CLUSTERED (id) ); CREATE TABLE dbo.withLob256 ( id int NOT NULL, extradata varchar(max) NOT NULL DEFAULT (REPLICATE(CONVERT(varchar(max),'x'), 256000)), CONSTRAINT pk_withLob256 PRIMARY KEY CLUSTERED (id) );
それぞれに100,000行を入力しました:
INSERT dbo.withJustId (id) SELECT TOP (100000) id = ROW_NUMBER() OVER (ORDER BY c1.name) FROM sys.all_columns AS c1 CROSS JOIN sys.all_objects; INSERT dbo.withoutLob (id) SELECT id FROM dbo.withJustId; INSERT dbo.withLob004 (id) SELECT id FROM dbo.withJustId; INSERT dbo.withLob016 (id) SELECT id FROM dbo.withJustId; INSERT dbo.withLob064 (id) SELECT id FROM dbo.withJustId; INSERT dbo.withLob256 (id) SELECT id FROM dbo.withJustId;
上記は非現実的であることを認めます。識別子+LOBデータだけのテーブルはどのくらいの頻度でありますか?これらの追加の4つの列を使用してテストを再度実行し、非LOBデータページにもう少し現実的な実体を与えました。
fill1 char(320) NOT NULL DEFAULT ('x'), count1 int NOT NULL DEFAULT (0), count2 int NOT NULL DEFAULT (0), dt datetime2 NOT NULL DEFAULT sysutcdatetime(),
これらのテーブルは全体のサイズの点でわずかに大きいだけですが、非LOBデータの量の比例した増加(このグラフには示されていません)は大きな違いですが、隠れています:
予約済みのテーブルサイズ(GB単位)
テスト
次に、これらの各操作のログデータを計測して収集しました(ONLINE = ON
の有無にかかわらず) )テーブルの各バリエーションに対して:
ALTER TABLE dbo.<name> DROP CONSTRAINT pk_<name>; ALTER TABLE dbo.<name> ALTER COLUMN id bigint NOT NULL; -- WITH (ONLINE = ON); ALTER TABLE dbo.<name> ADD CONSTRAINT pk_<name> PRIMARY KEY CLUSTERED (id);
実際には、動的SQLを使用してこれらすべてのテストを生成したため、各テストの前に手動でスクリプトをいじる必要はありませんでした。
別の投稿では、これらのテストの生成に使用した動的SQLを共有し、各ステップでタイミングを収集します。
比較のために、Andyの方法もテストしました(ただし、バッチ処理は行わず、テーブルのスキニーバージョンでのみ):
CREATE TABLE dbo.<name>_copy ( id bigint NOT NULL -- <, extradata column when relevant > CONSTRAINT pk_copy_<name> PRIMARY KEY CLUSTERED (id)); INSERT dbo.<name>_copy SELECT * FROM dbo.<name>; EXEC sys.sp_rename N'dbo.<name>', N'dbo.<name>_old', N'OBJECT'; EXEC sys.sp_rename N'dbo.<name>_copy', N'dbo.<name>', N'OBJECT';
ここでは幅の広いテーブルをスキップしました。バッチ操作のコーディングと測定の複雑さを紹介したくありませんでした。ここでの明らかな問題点は、列をインプレースで変更するのとは異なり、シャドウメソッドではそのLOBデータのすべてのバイトをコピーする必要があることです。バッチ処理は、単一のトランザクションでそれを実行しようとすることによる大きな影響を最小限に抑えることができますが、そのすべてのシャッフルは、最終的にはダウンストリームでやり直す必要があります。ソースでのバッチ処理では、宛先でどれだけのダメージを与えるかを完全に制御することはできません。
結果
ここで示す最初の結果は、12のテーブル構成すべてについて、ONLINE = ON
がある場合とない場合の、インプレース変更の平均期間です。 :
列をインプレースで変更する時間(秒単位)
これをオンライン操作として実行するには時間がかかりますが(最悪の場合は200秒)、ユーザーをブロックすることはありません。サイズとともに増加するように見えますが、完全に直線的ではありません。この操作をオフラインで実行するとブロッキングが発生しますが、はるかに高速であり、テーブルが大きくなるほど大幅に変化することはありません(最大サイズでも、これは約1分で発生します)。
これらのインプレース操作をスワップアンドドロップ操作と比較することは、スケールが大きく異なるため、折れ線グラフを使用して行うことは困難です。代わりに、各テーブル構成に関連する期間の横棒グラフを表示します。再作成が高速になったら、その行の背景を緑色にペイントします。遅い場合(またはオフラインとオンラインの方法の間にある場合)、おそらくその必要はありませんが、その行の背景を赤で塗りつぶします。
LOBサイズ|アプローチ|テーブル構成 | 期間(秒) | ||
---|---|---|---|
ちょうどId | ALTERオフライン | スキニーテーブル(10 MB) | 8.8 |
ワイドテーブル(30 MB) | 6.3 | ||
ALTER Online | スキニーテーブル | 11.0 | |
ワイドテーブル | 13.6 | ||
再作成 | スキニーテーブル | 3.4 | |
varchar 4K | オフライン | スキニーテーブル(390 MB) | 16.6 |
ワイドテーブル(780 MB) | 14.0 | ||
オンライン | スキニーテーブル | 30.4 | |
ワイドテーブル | 48.6 | ||
再作成 | スキニーテーブル | 1,290.0 | |
max 4k | オフライン | スキニーテーブル(390 MB) | 33.1 |
ワイドテーブル(780 MB) | 32.1 | ||
オンライン | スキニーテーブル | 81.9 | |
ワイドテーブル | 103.3 | ||
再作成 | スキニーテーブル | 28.9 | |
最大16k | オフライン | スキニーテーブル(1.6 GB) | 53.3 |
ワイドテーブル(1.7 GB) | 46.7 | ||
オンライン | スキニーテーブル | 130.9 | |
ワイドテーブル | 150.2 | ||
再作成 | スキニーテーブル | 81.8 | |
max 64k | オフライン | スキニーテーブル(7.0 GB) | 51.5 |
ワイドテーブル(7.1 GB) | 58.5 | ||
オンライン | スキニーテーブル | 136.5 | |
ワイドテーブル | 152.6 | ||
再作成 | スキニーテーブル | 226.5 | |
最大256k | オフライン | スキニーテーブル(25.8 GB) | 60.9 |
ワイドテーブル(25.9 GB) | 61.3 | ||
オンライン | スキニーテーブル | 149.1 | |
ワイドテーブル | 197.1 | ||
再作成 | スキニーテーブル | 1,576.7 |
これは、アンディの方法では不公平な揺れです。現実の世界では、その操作全体を1回のショットで実行することはできないからです。簡潔にするために、ここではトランザクションログの使用法を示しませんでしたが、サイドバイサイド操作でもバッチ処理することで、トランザクションログの使用法を制御する方が簡単です。彼のアプローチは前もってより多くの作業を必要としますが、ダウンタイムやブロッキングの点ではるかに安全です。ただし、行外のデータが多く、短時間の停止が可能である場合は、列を直接変更する方がはるかに簡単であることがわかります。 「大きすぎてインプレースで変更できない」は主観的なものであり、「大きい」の意味によって異なる結果を生み出す可能性があります。インプレース操作は許容できるトレードオフを表す可能性があるため、アプローチをコミットする前に、妥当なコピーに対して変更をテストすることが理にかなっている場合があります。
結論
アンディと議論するためにこれを書いたのではありません。元の投稿のアプローチは健全で、100%信頼性があり、常に使用しています。ただし、ブルートフォースが外科的精度よりも重視される場合、特にダウンタイムを少しでも取ることができる場合は、特定のテーブル形状に対してより単純なアプローチに価値がある可能性があります。