最小限のロギングの達成 INSERT ... SELECT
を使用 複雑なビジネスになる可能性があります。データ読み込みパフォーマンスガイドに記載されている考慮事項はまだかなり包括的ですが、SQLServerTigerチームのParikshitSavjaniによるSQLServer2016、最小ロギング、および一括読み込み操作でのバッチサイズの影響を読んで、 SQL Server 2016以降、クラスター化された行ストアテーブルに一括読み込みする場合。とはいえ、この記事は純粋に新しい詳細の提供に関係しています。 最小限のロギングについて INSERT ... SELECT
を使用して従来の(「メモリ最適化」ではない)ヒープテーブルを一括ロードする場合 。このシリーズのパート2では、bツリークラスター化インデックスを持つテーブルについて個別に説明します。
INSERT ... SELECT
を使用して行を挿入する場合 非クラスター化インデックスのないヒープに、ドキュメントには、そのような挿入は最小限にログに記録されると一般的に記載されています。 TABLOCK
である限り ヒントがあります。これは、データ読み込みパフォーマンスガイドに含まれている概要表に反映されています。 とタイガーチームのポスト。インデックスのないヒープテーブルの要約行は、両方のドキュメントで同じです(SQL Server 2016の変更はありません):
明示的なTABLOCK
ヒントは、テーブルレベルのロックの要件を満たす唯一の方法ではありません 。 「バルクロードのテーブルロック」を設定することもできます sp_tableoption
を使用したターゲットテーブルのオプション または、文書化されたトレースフラグ715を有効にします。(注: INSERT ... SELECT
を使用する場合、これらのオプションは最小限のロギングを有効にするのに十分ではありません。 INSERT ... SELECT
一括更新ロックはサポートされていません)。
「同時可能」 概要の列は、 INSERT ... SELECT
以外の一括読み込みメソッドにのみ適用されます 。ヒープテーブルの同時ロードは不可能です INSERT ... SELECT
を使用 。 データ読み込みパフォーマンスガイドに記載されているとおり 、 INSERT ... SELECT
を使用した一括読み込み 排他的を取ります X
一括更新ではなく、テーブルをロックします BU
同時バルクロードにはロックが必要です。
それはさておき、インデックス付けされていないヒープを TABLOCK
で一括ロードするときに、最小限のロギングを期待しない理由が他にないと仮定します。 (または同等のもの)—インサートはまだない可能性があります 最小限のログに記録される…
次のデモスクリプトは、新しいテストデータベースの開発インスタンスで実行する必要があります。 SIMPLE
を使用するように設定 リカバリーモデル。 INSERT ... SELECT
を使用して、多数の行をヒープテーブルにロードします。 TABLOCK
を使用 、および生成されたトランザクションログレコードに関するレポート:
CREATE TABLE dbo.TestHeap ( id integer NOT NULL IDENTITY, c1 integer NOT NULL, padding char(45) NOT NULL DEFAULT '' ); GO -- Clear the log CHECKPOINT; GO -- Insert rows INSERT dbo.TestHeap WITH (TABLOCK) (c1) SELECT TOP (897) CHECKSUM(NEWID()) FROM master.dbo.spt_values AS SV; GO -- Show log entries SELECT FD.Operation, FD.Context, FD.[Log Record Length], FD.[Log Reserve], FD.AllocUnitName, FD.[Transaction Name], FD.[Lock Information], FD.[Description] FROM sys.fn_dblog(NULL, NULL) AS FD; GO -- Count the number of fully-logged rows SELECT [Fully Logged Rows] = COUNT_BIG(*) FROM sys.fn_dblog(NULL, NULL) AS FD WHERE FD.Operation = N'LOP_INSERT_ROWS' AND FD.Context = N'LCX_HEAP' AND FD.AllocUnitName = N'dbo.TestHeap';
出力は、897行すべてが完全にログに記録されたことを示しています 最小限のログ記録のすべての条件を明らかに満たしているにもかかわらず(スペース上の理由から、ログレコードのサンプルのみが表示されています):
挿入を繰り返しても同じ結果が得られます(つまり、ヒープテーブルが空であるかどうかは関係ありません)。この結果はドキュメントと矛盾します。
1つのINSERT... SELECT
に追加する必要のある行数 最小限のロギングを達成するためのステートメント テーブルロックが有効になっているインデックス付けされていないヒープに変換するかどうかは、SQLServerが合計サイズを見積もるときに実行する計算によって異なります。 挿入されるデータの。この計算への入力は次のとおりです。
- SQLServerのバージョン。
- 挿入につながる推定行数 オペレーター。
- ターゲットテーブルの行サイズ。
SQLServer2012以前の場合 、この特定のテーブルの遷移点 898行です 。デモスクリプトの番号を変更するTOP
897から898までの句は、次の出力を生成します。
生成されるトランザクションログエントリは、ページの割り当てとインデックス割り当てマップの維持に関係しています。 (IAM)およびページの空き容量 (PFS)構造。 最小限のロギングを忘れないでください これは、SQLServerが各行の挿入を個別にログに記録しないことを意味します。代わりに、メタデータと割り当て構造への変更のみがログに記録されます。 897行から898行に変更すると、最小限のログが有効になります この特定のテーブルの場合。
SQLServer2014以降の場合 、遷移点は950行です このテーブルのために。 INSERT ... SELECT
を実行する TOP(949)
を使用 フルロギングを使用します – TOP(950)
に変更 最小限のロギングを生成します 。
しきい値はない カーディナリティ推定に依存 使用中のモデルまたはデータベースの互換性レベル。
SQLServerが行セット一括読み込みの使用を決定するかどうか —したがって、最小限のロギング 使用可能かどうか— sqllang!CUpdUtil ::FOptimizeInsert
というメソッドで実行された一連の計算の結果によって異なります 、 trueを返します 最小限のロギングの場合、または false フルロギング用。コールスタックの例を以下に示します。
テストの本質は次のとおりです。
- 挿入は250行以上である必要があります 。
- 挿入データの合計サイズは、少なくとも8ページとして計算する必要があります。 。
250行を超えるかどうかのチェックは、テーブル挿入に到達する推定行数にのみ依存します。 オペレーター。これは、実行プランに「推定行数」として表示されます。 。これに注意してください。たとえば、 TOP
で変数を使用することにより、推定行数が少ないプランを簡単に作成できます。 OPTION(RECOMPILE)
のない句 。その場合、オプティマイザーは100行を推測しますが、これはしきい値に達しないため、バルクロードと最小限のロギングを防ぎます。
合計データサイズの計算はより複雑で、一致しません 「推定行サイズ」 テーブル挿入に流れ込む オペレーター。計算の実行方法は、SQLServer2012以前とSQLServer2014以降ではわずかに異なります。それでも、どちらも実行プランに表示されるものとは異なる行サイズの結果を生成します。
挿入データの合計サイズは、推定行数を掛けて計算されます。 予想される最大行サイズ 。行サイズの計算は、SQLServerのバージョン間で異なる点です。
SQL Server 2012以前では、計算は sqllang!OptimizerUtil ::ComputeRowLength
によって実行されます。 。テストヒープテーブルの場合(元の FixedVar を使用して、単純な固定長のnull以外の列で意図的に設計されています 行ストレージ形式)計算の概要は次のとおりです。
- FixedVarを初期化します メタデータジェネレータ。
- Table Insertの各列のタイプと属性の情報を取得します 入力ストリーム。
- 入力した列と属性をメタデータに追加します。
- ジェネレーターを完成させて、最大行サイズを尋ねます。
- nullビットマップのオーバーヘッドを追加します と列の数。
- 行のステータスビットに4バイトを追加します 列数データへの行オフセット。
この計算の結果は、物理的な行サイズと一致すると予想される場合がありますが、一致しません。たとえば、データベースの行のバージョン管理がオフになっている場合:
SELECT DDIPS.index_type_desc, DDIPS.alloc_unit_type_desc, DDIPS.page_count, DDIPS.record_count, DDIPS.min_record_size_in_bytes, DDIPS.max_record_size_in_bytes, DDIPS.avg_record_size_in_bytes FROM sys.dm_db_index_physical_stats ( DB_ID(), OBJECT_ID(N'dbo.TestHeap', N'U'), 0, -- heap NULL, -- all partitions 'DETAILED' ) AS DDIPS;
…60バイトのレコードサイズを提供します テストテーブルのすべての行:
これは、「ヒープのサイズの見積もり:
」で説明されているとおりです。- すべての固定長の合計バイトサイズ 列=53バイト:
-
id integer NOT NULL
=4バイト -
c1 integer NOT NULL
=4バイト -
padding char(45)NOT NULL
=45バイト。
-
- ヌルビットマップ =3バイト :
- =2 + int(( Num_Cols + 7)/ 8)
- =2 + int((3 + 7)/ 8)
- =3バイト。
- 行ヘッダー =4バイト 。
- 合計53+3 + 4 =60バイト 。
また、実行プランに示されている推定行サイズと一致します:
バルクロードが使用されているかどうかを判断するために使用される内部計算では、次のストリームの挿入に基づいて、異なる結果が得られます。 デバッガーを使用して取得した列情報。使用されるタイプ番号はsys.types
と一致します :
- 合計固定長 列サイズ=66バイト :
- タイプID173
binary(8)
=8バイト(内部)。 - タイプID56
integer
=4バイト(内部)。 - タイプID104
ビットコード> =1バイト(内部)。
- タイプID56
integer
=4バイト(id
列)。 - タイプID56
integer
=4バイト(c1
列)。 - タイプID175
char(45)
=45バイト(パディング
列)。
- タイプID173
- ヌルビットマップ =3バイト (以前と同じように)。
- 行ヘッダー オーバーヘッド=4バイト (以前と同じように)。
- 計算された行サイズ=66+ 3 + 4 =73バイト 。
違いは、 Table Insertにフィードする入力ストリームです。 演算子には3つの追加の内部列が含まれています 。これらは、showplanが生成されるときに削除されます。追加の列は、テーブル挿入ロケーターを構成します 、最初のコンポーネントとしてブックマーク(RIDまたは行ロケーター)が含まれます。 メタデータです 挿入用であり、テーブルに追加されることはありません。
追加の列は、 OptimizerUtil ::ComputeRowLength
によって実行される計算間の不一致を説明しています 行の物理的なサイズ。これはバグと見なすことができます :SQL Serverは、挿入ストリーム内のメタデータ列を行の最終的な物理サイズにカウントしないようにする必要があります。一方、計算は、一般的な updateを使用したベストエフォートの見積もりである可能性があります。 オペレーター。
計算では、行のバージョン管理の14バイトのオーバーヘッドなどの他の要因も考慮されていません。これは、スナップショットアイソレーションのいずれかを使用してデモスクリプトを再実行することでテストできます。 またはコミットされたスナップショットアイソレーションを読み取る データベースオプションが有効になっています。行の物理サイズは14バイト増加します(60バイトから74バイトに)が、最小ロギングのしきい値 898行で変更されません。
これで、SQLServer2012以前のこのテーブルのしきい値が898行である理由を確認するために必要なすべての詳細が得られました。
- 898行は250行以上の最初の要件を満たしています 。
- 計算された行サイズ=73バイト。
- 推定行数=897。
- 合計データサイズ=73バイト*897行=65481バイト。
- 合計ページ数=65481/8192=7.9932861328125。
- これは、8ページ以上の2番目の要件のすぐ下です。
- 898行の場合、ページ数は8.02197265625です。
- これは>=8ページです したがって、最小限のロギング がアクティブになります。
SQLServer2014以降の場合 、変更点は次のとおりです。
- 行サイズはメタデータジェネレーターによって計算されます。
- テーブルロケーターの内部整数列 挿入ストリームには存在しなくなりました。これは一意化子を表します 、これはインデックスにのみ適用されます。これはバグ修正として削除された可能性があります。
- 予想される行サイズが73バイトから69バイトに変更されます 整数列(4バイト)が省略されているため。
- 物理サイズはまだ60バイトです。残りの9バイトの違いは、挿入ストリームの余分な8バイトのRIDと1バイトのビットの内部列によって説明されます。
1行あたり69バイトで8ページのしきい値に達するには:
- 8ページ*1ページあたり8192バイト=65536バイト。
- 65535バイト/行あたり69バイト=949.7971014492754行。
- したがって、少なくとも950行が必要です。 行セットの一括読み込みを有効にするには SQLServer2014以降のこのテーブルの場合。
バッチサイズをサポートする一括読み込み方法とは対照的です 、Parikshit Savjaniによる投稿でカバーされているように、 INSERT ... SELECT
インデックス付けされていないヒープ(空かどうか)に入ると、常にとは限りません。 テーブルロックが指定されている場合、ログは最小限に抑えられます。
INSERT ... SELECT
で最小限のログを有効にするには 、SQLServerは250行以上を期待する必要があります 合計サイズが少なくとも1つのエクステント (8ページ)。
推定合計挿入サイズを計算する場合(8ページのしきい値と比較するため)、SQLServerは推定行数に計算された最大行サイズを掛けます。 SQLServerは内部列をカウントします 行サイズを計算するときに挿入ストリームに存在します。 SQL Server 2012以前の場合、これにより1行あたり13バイトが追加されます。 SQL Server 2014以降では、1行あたり9バイトが追加されます。これは計算にのみ影響します。行の最終的な物理サイズには影響しません。
最小限のログのヒープバルクロードがアクティブな場合、SQLServerはしません 行を1つずつ挿入します。エクステントは事前に割り当てられ、挿入される行は sqlmin!RowsetBulk
によってまったく新しいページに収集されます。 既存の構造に追加される前。コールスタックの例を以下に示します。
論理読み取りは報告されません 最小限のログのヒープバルクロードが使用される場合のターゲットテーブルの場合– Table Insert オペレーターは、新しい行ごとに挿入ポイントを見つけるために既存のページを読み取る必要はありません。
現在、実行計画は表示されません 行セットの一括読み込みを使用して挿入された行またはページの数 および最小限のロギング 。おそらく、この有用な情報は、将来のリリースで製品に追加される予定です。