web-dev-qa-db-ja.com

nvarchar(max)列をnvarchar(50)に変更すると、テーブルがロックされますか?

SQL Server(Azure)に140万行のテーブルがあります。

列の1つにインデックスを付けたいです。ただし、そのデータ型はnvarchar(max)です(デフォルトでは、想像上のEFを使用してそのようになっています)。私はEntity Frameworkを使用しており、このデータ型をnvarchar(50)に変更する移行を行うことができます。

私の懸念は、これにかかる時間と、それがロックの問題を引き起こすかどうかです。列を展開したことがあるので、何を期待すればよいかわかりません。この列のデータは新しいサイズでうまく収まるでしょう。

データベースのコピーを作成して試してみるつもりでした。新しい列への移行も検討しましたが、現在の列があまり痛くない場合は更新することをお勧めします。

5
Chris Stewart

私たちがあなたの質問に完全に答える方法はありません。テーブル定義、テーブル内の他の列、データがページ外にある場合、ORMによって生成されたT-SQLなど、非常に多くの要因に依存します。 documentation はかなり良いですが:

WITH(ONLINE = ON | OFF)適用対象:SQL Server 2016〜SQL Server 2017およびAzure SQL Database。

テーブルを使用可能な状態のまま、列の変更アクションを多数実行できるようにします。デフォルトはオフです。列の変更は、データ型、列の長さまたは精度、ヌル可能性、スパース性、および照合に関連する列の変更のために、オンラインで実行できます。

オンライン変更列を使用すると、ユーザーが作成した自動統計で、ALTER COLUMN操作の間、変更された列を参照できます。これにより、クエリを通常どおり実行できます。操作の最後に、列を参照する自動統計が削除され、ユーザーが作成した統計は無効になります。ユーザーは、操作が完了した後、ユーザーが生成した統計を手動で更新する必要があります。列が統計またはインデックスのフィルター式の一部である場合、列の変更操作を実行できません。

オンラインの列変更操作の実行中、列に依存する可能性のあるすべての操作(インデックス、ビューなど)はブロックされるか、適切なエラーで失敗します。これにより、操作の実行中に導入された依存関係が原因で、オンライン変更列が失敗しないことが保証されます。

オンライン変更列には、オンラインインデックスの再構築と同様の要件、制限、および機能があります。これも:

テーブルにレガシーLOBまたはファイルストリーム列が含まれている場合、またはテーブルに列ストアインデックスがある場合、オンラインインデックスの再構築はサポートされません。オンライン制限列にも同じ制限が適用されます。

変更される既存の列には、2倍のスペース割り当てが必要です。元の列と新しく作成された非表示列。

列の変更オンライン操作中のロック戦略は、オンラインインデックスの構築に使用されるのと同じロックパターンに従います。

さらに、テストも非常に簡単です。私はSQL Server 2017で、デフォルトのコミット読み取り分離レベルでテストしています。まず、テーブルを作成します。

_DROP TABLE IF EXISTS dbo.CONVERT_ME;

CREATE TABLE dbo.CONVERT_ME (
    ID BIGINT NOT NULL,
    OVERWEIGHT_COLUMN VARCHAR(MAX) NOT NULL,
    PRIMARY KEY (ID)
);

INSERT INTO dbo.CONVERT_ME WITH (TABLOCK)
SELECT TOP (1500000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)), REPLICATE('Z', 50)
FROM master..spt_values t1
CROSS JOIN master..spt_values t2
OPTION (MAXDOP 1);
_

次に、デフォルトの列OFFLINEを変更します。

_SET STATISTICS IO, TIME ON;

BEGIN TRANSACTION;

ALTER TABLE dbo.CONVERT_ME
ALTER COLUMN OVERWEIGHT_COLUMN VARCHAR(50) NOT NULL WITH (ONLINE = OFF);
_

私のマシンはこれに約3秒かかります。操作は、トランザクション全体でスキーマ変更ロックを取得します。これにより、NOLOCKでのクエリを含め、他のロックが取得されなくなります。

enter image description here

次に、WITH _(ONLINE = ON)_で試してみます。これで、操作は並列処理の対象になるため、サーバーでより高速に実行される可能性があります。スキーマ変更ロックはオブジェクトに対して引き続き行われますが、トランザクション全体ではなく、終わり近くに発生します。以下は、ほとんどのトランザクション中に保持されているスキーマ変更ロックを示す、ロックテーブルのスナップショットの例です。

enter image description here

オブジェクトレベルのスキーマ変更ロックはまだ取得されていないことに注意してください。操作が完了した後、トランザクションがコミットする前の状態は次のとおりです。

enter image description here

オブジェクトのスキーマ変更ロックは、他のいくつかのオブジェクトとともに、最後の方で短時間行われます。

すばらしいですが、WITH (ONLINE = ON)を使用しても問題はありませんよね?前に言ったように、操作にかかる時間は多くの要因に依存します。さらに多くのデータページがある別のテーブルを考えてみます。

_DROP TABLE IF EXISTS dbo.CONVERT_ME;

CREATE TABLE dbo.CONVERT_ME (
    ID BIGINT NOT NULL,
    OTHER_COLUMN VARCHAR(8000) NOT NULL,
    OVERWEIGHT_COLUMN VARCHAR(MAX) NOT NULL,
    PRIMARY KEY (ID)
);

INSERT INTO dbo.CONVERT_ME WITH (TABLOCK)
SELECT TOP (1500000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)), REPLICATE('Z', 4200), REPLICATE('Z', 50)
FROM master..spt_values t1
CROSS JOIN master..spt_values t2
OPTION (MAXDOP 1);
_

私のマシンでは、同じ_ALTER TABLE_がOFFLINEを実行すると39635ミリ秒、ONLINEを実行すると117877ミリ秒かかります。

ロックに関する考慮事項を確実に理解できるまでドキュメントを読み、本番環境での切り替えを行う前に、実際のデータが入力された実際のテーブルのスキーマでテストすることをお勧めします。

ご注意ください:

Azure SQL Databaseについては不明ですが、通常のSQL Serverでは、ONLINEオプションはEnterprise Edition専用です。別のエディションでこれを実行しようとすると、次のエラーが発生します。

メッセージ1712、レベル16、状態1、行XXXXX
オンラインインデックス操作は、SQL ServerのEnterpriseエディションでのみ実行できます。

9
Joe Obbish

それはテーブルをロックします、あなたが尋ねた問題は「どれくらいの長さですか?」です。そして、その答えは(いつものように)「依存する」です。その時に他に何が実行されていますか? Azure(または任意のサーバー)が行を更新する速度を教えてください。唯一の方法は、そのとき実行しているのと同じ負荷を実行している同じ構成のサーバーに対してそれをテストすることです。

別の方法として、以前に新しい列をテーブルに追加することに成功しました(テーブルを変更し、列varchar(50)nullを追加します)。次に、データをコピーします。これは単一の更新ステートメント(または、ロックの問題が発生する可能性があります)またはバッチ処理することができ、一度に1つのレコードしか実行しないアプリを作成することもできます。すべてがコピーされたら、古い列をドロップし、新しい列の名前を変更します。

リスクは、データをコピーしてから、データが変更された古い列を削除するまでの間です。トリガー(Azureのトリガーについては不明)を使用してこれを軽減し、2つの列間でデータの同期を維持できます。

2
Greg