ALTER TABLE ... ALTER COLUMN コマンドは非常に強力です。これを使用して、列のデータ型、長さ、精度、スケール、null可能性、照合など、その他多くのことを変更できます。
確かに、他の方法よりも便利です。新しいテーブルを作成し、変更が必要になるたびにデータを移行します。それにもかかわらず、根底にある複雑さを隠すためにできることはたくさんあります。このコマンドで何が可能であるかについての多数の制限に加えて、常にパフォーマンスの問題があります。
最終的に、テーブルは一連のバイトとして格納され、システム内の他の場所にメタデータが含まれて、それらの各バイトの意味と、テーブルのさまざまな列のそれぞれとの関係を説明します。 SQL Serverに列の定義の一部を変更するように依頼する場合、既存のデータが新しい定義と互換性があることを確認する必要があります。また、現在の物理レイアウトを変更する必要があるかどうかを判断する必要があります。
変更の種類とデータベースの構成に応じて、ALTER COLUMN コマンドは、次のいずれかのアクションを実行する必要があります。
- システムテーブルのメタデータのみを変更します。
- 既存のすべてのデータの互換性を確認してから、メタデータを変更します。
- 保存されているデータの一部またはすべてを新しい定義に一致するように書き直します。
オプション1は、パフォーマンスの観点から理想的なケースを表しています。システムテーブルへのわずかな変更と最小限のロギングが必要です。操作には、制限的なスキーマ変更が必要ですSch-M ロックしますが、メタデータの変更自体は、テーブルのサイズに関係なく、非常に迅速に完了します。
注意すべき特殊なケースがいくつかありますが、一般的な要約として、次のアクションではメタデータの変更のみが必要です。
-
NOT NULLからの移行NULLへ 同じデータ型の場合。 -
varcharの最大サイズを大きくする 、nvarchar、またはvarbinary列(maxを除く 。
SQLServer2016の改善
この投稿の主題は、メタデータに対してのみ有効になる追加の変更です- SQLServer2016以降 。構文を変更したり、構成設定を変更したりする必要はありません。これらの文書化されていない改善を無料で入手できます。
新しい機能は、固定長のサブセットを対象としています データ型。新しい機能は、次の状況で行ストアテーブルに適用されます。
- 圧縮を有効にする必要があります:
- すべてのインデックスとパーティション 、ベースヒープまたはクラスター化インデックスを含みます。
-
ROWのいずれか またはPAGE圧縮。 - インデックスとパーティションは混合物を使用する場合があります これらの圧縮レベルの。重要なのは、非圧縮のインデックスやパーティションがないことです。
-
NULLからの変更NOT NULLへ 許可されていません 。 - 次の整数型の変更 サポートされています:
-
smallintintegerまたはbigint。 integerbigintへ 。-
smallmoneymoneyへ (内部的に整数表現を使用します)。
-
- 次の文字列とバイナリタイプの変更 サポートされています:
-
char(n)char(m)へ またはvarchar(m) -
nchar(n)nchar(m)へ またはnvarchar(m) -
binary(n)binary(m)へ またはvarbinary(m) - 上記のすべては
n < mの場合のみ およびm != max - 照合の変更は許可されていません
-
Column Descriptor の場合、基になるバイナリデータレイアウトは変更されないため、これらの変更はメタデータのみになります。 行形式が使用されます(したがって、圧縮が必要です)。圧縮しない場合、行ストアは元の FixedVarを使用します 表現。物理的なレイアウトを書き直さないと、これらの固定長のデータ型の変更に対応できません。
tinyintに気付くかもしれません 整数型リストから省略されています。これは、他の整数型はすべて符号付きであるのに対し、符号なしであるため、メタデータのみの変更は不可能であるためです。たとえば、tinyintの場合、255の値を1バイトに収めることができます。 、ただし、署名された形式のいずれかで2バイトが必要です。署名された形式は、圧縮時に1バイトで-128〜+127を保持できます。
この改善の非常に便利なアプリケーションの1つは、IDENTITYを使用して列のデータ型を変更することです。 プロパティ。
行圧縮を使用する次のヒープテーブルがあるとします(ページ圧縮も機能します):
DROP TABLE IF EXISTS dbo.Test;
GO
CREATE TABLE dbo.Test
(
id integer IDENTITY NOT NULL,
some_value integer NOT NULL
)
WITH (DATA_COMPRESSION = ROW); 500万行のデータを追加しましょう。これは、列のデータ型の変更がメタデータのみの操作であるかどうかを(パフォーマンスの観点から)明らかにするのに十分です。
WITH Numbers AS
(
SELECT
n = ROW_NUMBER() OVER (ORDER BY @@SPID)
FROM sys.all_columns AS AC1
CROSS JOIN sys.all_columns AS AC2
ORDER BY n
OFFSET 0 ROWS
FETCH FIRST 5 * 1000 * 1000 ROWS ONLY
)
INSERT dbo.Test
WITH (TABLOCKX)
(
some_value
)
SELECT
N.n
FROM Numbers AS N;
次に、IDENTITYを再シードします integerに収まる値がほぼ不足しているように見せるため :
DBCC CHECKIDENT
(
N'dbo.Test',
RESEED,
2147483646
); もう1行正常に追加できます:
INSERT dbo.Test
(some_value)
VALUES
(123456); しかし、別の行を追加しようとしています:
INSERT dbo.Test
(some_value)
VALUES
(7890); エラーメッセージが表示されます:
メッセージ8115、レベル16、状態1、行1IDENTITYをデータ型intに変換する算術オーバーフローエラー。
列をbigintに変換することで、これを修正できます。 :
ALTER TABLE dbo.Test ALTER COLUMN id bigint NOT NULL;
SQL Server 2016の改善により、このコマンドはメタデータのみを変更します 、およびすぐに完了します。前のINSERT ステートメント(算術オーバーフローエラーをスローしたステートメント)が正常に完了するようになりました。
この新しい機能は、IDENTITYで列のタイプを変更する際のすべての問題を解決するわけではありません。 財産。それでも、列のインデックスを削除して再作成したり、参照する外部キーを再作成したりする必要があります。これは、この投稿の範囲外です(ただし、Aaron Bertrandは以前にそれについて書いています)。メタデータのみの操作としてタイプを変更できることは、確かに害にはなりません。慎重に計画することで、必要な他の手順を可能な限り効率的に行うことができます。たとえば、最小限のログまたはONLINEを使用します。 操作。
必ず常に NULLを指定します またはNOT NULL ALTER COLUMNでデータ型を変更する場合 。たとえば、some_valueのデータ型も変更したいとします。 integer NOT NULLのテストテーブルの列 bigint NOT NULLへ 。
コマンドを作成するときは、NULLを省略します またはNOT NULL 修飾子:
ALTER TABLE dbo.Test ALTER COLUMN some_value bigint;
このコマンドは、メタデータのみの変更として正常に完了しますが、NOT NULLも削除します 制約。列はbigint NULLになりました 、これは私たちが意図したものではありません。この動作は文書化されていますが、見落としがちです。
間違いを修正しようとするかもしれません:
ALTER TABLE dbo.Test ALTER COLUMN some_value bigint NOT NULL;
これはありません メタデータのみの変更。 NULLからの変更は許可されていません NOT NULLへ (条件の復習が必要な場合は、前の表に戻って参照してください)。 SQL Serverは、既存のすべての値をチェックして、nullが存在しないことを確認する必要があります。次に、物理的にすべての行を書き換えます テーブルの。これらのアクションは、それ自体が遅いだけでなく、大量のトランザクションログを生成し、ノックオン効果をもたらす可能性があります。
ちなみに、IDENTITYの列では、これと同じ間違いは起こり得ません。 財産。 ALTER COLUMNを記述した場合 NULLのないステートメント またはNOT NULL その場合、エンジンは、NOT NULLを意味していると想定します。 これは、IDプロパティがNULL可能列では許可されていないためです。この振る舞いに頼らないことは、今でも素晴らしい考えです。
常にNULLを指定してください またはNOT NULL ALTER COLUMNを使用 。
データベースのデフォルトと一致しない照合を持つ文字列列を変更する場合は、特に注意が必要です。
たとえば、大文字と小文字が区別され、アクセントに依存する照合を含むテーブルがあるとします(データベースのデフォルトが異なると仮定します):
DROP TABLE IF EXISTS dbo.Test2;
GO
CREATE TABLE dbo.Test2
(
id integer IDENTITY NOT NULL,
some_string char(8) COLLATE Latin1_General_100_CS_AS NOT NULL
)
WITH (DATA_COMPRESSION = ROW); 500万行のデータを追加します:
WITH Numbers AS
(
SELECT
n = ROW_NUMBER() OVER (ORDER BY @@SPID)
FROM sys.all_columns AS AC1
CROSS JOIN sys.all_columns AS AC2
ORDER BY n
OFFSET 0 ROWS
FETCH FIRST 5 * 1000 * 1000 ROWS ONLY
)
INSERT dbo.Test2
WITH (TABLOCKX)
(
some_string
)
SELECT
CONVERT(char(8), N.n) COLLATE Latin1_General_100_CS_AS
FROM Numbers AS N; 次のコマンドを使用して、文字列列の長さを2倍にします。
ALTER TABLE dbo.Test2 ALTER COLUMN some_string char(16) NOT NULL;
NOT NULLを指定することを忘れないでください 、しかしデフォルト以外の照合を忘れました。 SQL Serverは、照合をデータベースのデフォルト(Latin1_General_CI_AS)に変更することを意図していると想定しています。 私のテストデータベース用)。照合を変更すると、操作がメタデータのみになるのを防ぐため、操作は数分間実行され、大量のログが生成されます。
前のスクリプトを使用してテーブルとデータを再作成してから、ALTER COLUMNを試してください もう一度コマンドを実行しますが、コマンドの一部として既存のデフォルト以外の照合を指定します:
ALTER TABLE dbo.Test2
ALTER COLUMN some_string
char(16) COLLATE Latin1_General_100_CS_AS NOT NULL;
これで、メタデータのみの操作として、変更がすぐに完了します。 NULLと同様 およびNOT NULL 構文では、事故を避けるために明示的にすることが重要です。これは、ALTER COLUMNだけでなく、一般的に良いアドバイスです。 。
圧縮は、インデックスごとに明示的に指定する必要があり、ヒープの場合はベーステーブルに対して個別に指定する必要があることに注意してください。これは、省略された構文またはショートカットを使用すると、望ましい結果が得られない可能性がある別の例です。
たとえば、次の表では、主キーまたはインラインインデックス定義のいずれにも明示的な圧縮を指定していません。
CREATE TABLE dbo.Test
(
id integer IDENTITY NOT NULL PRIMARY KEY,
some_value integer NOT NULL
INDEX [IX dbo.Test some_value]
)
WITH (DATA_COMPRESSION = PAGE);
PRIMARY KEY 名前が割り当てられます。デフォルトはCLUSTEREDです。 、PAGEになります 圧縮されています。インラインインデックスはNONCLUSTEREDになります まったく圧縮されていません。すべてのインデックスとパーティションが圧縮されているわけではないため、このテーブルは新しい最適化では有効になりません。
はるかに優れた、より明示的なテーブル定義は次のようになります。
CREATE TABLE dbo.Test
(
id integer IDENTITY NOT NULL
CONSTRAINT [PK dbo.Test id]
PRIMARY KEY CLUSTERED
WITH (DATA_COMPRESSION = PAGE),
some_value integer NOT NULL
INDEX [IX dbo.Test some_value]
NONCLUSTERED
WITH (DATA_COMPRESSION = ROW)
); すべてのインデックスとパーティションが圧縮されているため、このテーブルは新しい最適化の対象になります。前述のように、圧縮タイプの混合は問題ありません。
このCREATE TABLEを作成するにはさまざまな方法があります 明示的な方法でステートメントを作成するため、個人的な好みの要素があります。重要なポイントは、常に明確にすることです。 あなたが欲しいものについて。これは、個別のCREATE INDEXに適用されます ステートメントも。
新しいメタデータ専用の拡張イベントがあります-ALTER COLUMNのみ SQLServer2016以降でサポートされる操作。
拡張イベントはcompressed_alter_column_is_md_onlyです デバッグで チャネル。そのイベントフィールドはobject_idです 、column_id 、およびis_md_only (true / false)。
このイベントは、SQL Server 2016の新機能により、操作がメタデータのみであるかどうかのみを示します。2016より前にメタデータのみであった列の変更には、is_md_only = falseが表示されます。 まだメタデータのみであるにもかかわらず。
ALTER COLUMNの追跡に役立つその他の拡張イベント 操作にはmetadata_ddl_alter_columnが含まれます およびalter_column_event 、両方とも Analytic チャネル。
無効にする必要がある場合 何らかの理由で新しいSQLServer2016機能を使用すると、文書化されていないグローバル(または起動)トレースフラグ3618を使用できます。このトレースフラグは、セッションレベルで使用する場合は効果的ではありません。 ALTER COLUMNでクエリレベルのトレースフラグを指定する方法はありません。 コマンド。
メタデータのみの変更で一部の固定長整数データ型を変更できることは、非常に歓迎される製品の改善です。テーブルがすでに完全に圧縮されている必要がありますが、とにかくそれはより一般的なものになりつつあります。これは、SQL Server 2016 ServicePack1以降のすべてのエディションで圧縮が有効になっているため特に当てはまります。
固定長の文字列型の列は、おそらくそれほど一般的ではありません。これの一部は、スペース使用量などのやや時代遅れの考慮事項が原因である可能性があります。圧縮された場合、固定長の文字列列は末尾の空白を格納しないため、格納の観点からは可変長の文字列列と同じくらい効率的です。操作や表示のためにスペースをトリミングするのは面倒な場合がありますが、データが通常最大長の大部分を占める場合、固定長タイプには、特に並べ替えやハッシュなどのメモリ付与に関して、重要な利点があります。
圧縮が有効になっていることは、すべて良いニュースではありません。 SQL Serverは、既存のすべての値が新しいタイプに正常に変換されることを確認した後、メタデータのみの変更を実行できる場合があることを前述しました。これは、ALTER COLUMNを使用する場合です。 integerから変更するには smallintへ 例えば。残念ながら、これらの操作は現在、圧縮されたオブジェクトのメタデータのみではありません。
Panagiotis Antonopoulosに感謝します (プリンシパルソフトウェアエンジニア)および Mirek Sztajno (シニアプログラムマネージャー)SQL Server製品チームから、この記事の調査と執筆中の支援とガイダンスに感謝します。
この作業で提供される詳細は、Microsoftの公式ドキュメントまたは製品ステートメントと見なされるべきではありません。