この投稿は新しい情報を提供します 最小ログバルクロードの前提条件について INSERT ... SELECT
を使用する場合 インデックス付きテーブルに 。
これらのケースを可能にする内部機能は、 FastLoadContext
と呼ばれます。 。文書化されたトレースフラグ610を使用して、SQL Server2008から2014までアクティブ化できます。SQLServer2016以降、 FastLoadContext
デフォルトで有効になっています。トレースフラグは必要ありません。
FastLoadContext
なし 、最小限のログ記録が可能な唯一のインデックス挿入 空にそれらがあります このシリーズのパート2で説明されているように、セカンダリインデックスのないクラスター化インデックス。 最小限のロギング インデックス付けされていないヒープテーブルの条件については、パート1で説明しました。
背景について詳しくは、データパフォーマンス読み込みガイドと Tiger Teamをご覧ください。 SQLServer2016の動作の変更に関する注意事項。
簡単なリマインダーとして、 RowsetBulk
ファシリティ(パート1および2で説明)により、最小限のログが可能になります バルクロード:
- 空および空でないヒープ 次のテーブル:
- テーブルロック;および
- セカンダリインデックスはありません。
- 空のクラスター化されたテーブル 、with:
- テーブルロック;および
- セカンダリインデックスはありません。および
-
DMLRequestSort =true
Clustered Index Insert オペレーター。
FastLoadContext
コードパスにより、最小限のログのサポートが追加されます および同時 バルクロード:
- 空および空でないクラスター化 bツリーインデックス。
- 空で空でないクラスター化されていない 専用によって維持されるbツリーインデックス インデックス挿入 プランオペレーター。
FastLoadContext
DMLRequestSort =true
も必要です すべての場合において、対応するプランオペレーターについて。
RowsetBulk
の重複に気付いたかもしれません およびFastLoadContext
セカンダリインデックスのない空のクラスタ化テーブルの場合。 TABLOCK
ヒントは不要です FastLoadContext
を使用 、ただし、欠席する必要はありません また。結果として、 TABLOCK
を使用した適切な挿入 最小限のロギングの対象となる可能性があります FastLoadContext
経由 詳細なRowsetBulk
が失敗した場合 テスト。
FastLoadContext
無効にすることができます 文書化されたトレースフラグ692を使用したSQLServer2016で。デバッグチャネル拡張イベントfastloadcontext_enabled
FastLoadContext
の監視に使用できます インデックスパーティション(行セット)ごとの使用量。このイベントは、 RowsetBulk
では発生しません ロード。
単一のINSERT... SELECT
FastLoadContext
を使用したステートメント 完全にログに記録する 最小限のロギング中のいくつかの行 その他。
行は一度に1つずつ挿入されます インデックス挿入による オペレーターと完全にログに記録された 次の場合:
- すべての行が最初のに追加されました インデックスが空だった場合のインデックスページ 操作の開始時。
- 既存に追加された行 インデックスページ。
- 行を移動 ページ分割によるページ間。
それ以外の場合は、順序付けられた挿入ストリームの行が新しいページに追加されます。 最適化された最小限のログを使用する コードパス。できるだけ多くの行が新しいページに書き込まれると、既存のターゲットインデックス構造に直接リンクされます。
新しく追加されたページは必ずしもではありません SQL Serverは、論理的に既存に属する新しいページに行を追加しないように注意する必要があるため、いっぱいになります(ただし、これは明らかに理想的なケースです)。 インデックスページ。新しいページは1つの単位としてインデックスに「ステッチ」されるため、新しいページに他の場所に属する行を含めることはできません。これは主に、内に行を追加するときに発生します。 既存のインデックスキー範囲の開始前または終了後ではなく、インデックスの既存のキー範囲。
まだ可能 内に新しいページを追加するには 既存のインデックスキーの範囲ですが、新しい行は前のの最も高いキーよりも上位に並べ替える必要があります 既存のインデックスページおよび 次のの一番下のキーより下に並べ替えます 既存のインデックスページ。 最小限のロギングを達成するための最良のチャンス このような状況では、挿入された行が既存の行と可能な限り重ならないようにしてください。
DMLRequestSort条件
FastLoadContext
であることを忘れないでください DMLRequestSort
の場合にのみアクティブ化できます trueに設定されています 対応するインデックス挿入 実行計画の演算子。
DMLRequestSort
を設定できる2つの主要なコードパスがあります true インデックス挿入用。 どちらかのパス trueを返す 十分です。
1。 FOptimizeInsert
sqllang!CUpdUtil ::FOptimizeInsert
コードに必要なもの:
- 250行以上 推定 挿入されます。 および
- 2ページ以上 推定 データサイズを挿入します。 および
- ターゲットインデックス リーフページが3ページ未満である必要があります 。
これらの条件は、 RowsetBulk
と同じです。 空のクラスター化インデックスで、2つ以下のインデックスリーフレベルページの追加要件があります。これは既存のインデックスのサイズを指していることに注意してください。 挿入前、しない 追加するデータの推定サイズ。
以下のスクリプトは、このシリーズの前半で使用したデモを変更したものです。 最小限のロギングを示しています 前に入力されたインデックスページが3つ未満の場合 テストINSERT... SELECT
実行されます。テストテーブルスキーマは、データベースの行バージョン管理がオフの場合に、130行が単一の8KBページに収まるようになっています。最初のTOP
の乗数 句を変更して、前の既存のインデックスページの数を決定できます。 テストINSERT... SELECT
実行されます:
IF OBJECT_ID(N'dbo.Test', N'U') IS NOT NULL BEGIN DROP TABLE dbo.Test; END; GO CREATE TABLE dbo.Test ( id integer NOT NULL IDENTITY CONSTRAINT [PK dbo.Test (id)] PRIMARY KEY, c1 integer NOT NULL, padding char(45) NOT NULL DEFAULT '' ); GO -- 130 rows per page for this table -- structure with row versioning off INSERT dbo.Test (c1) SELECT TOP (3 * 130) -- Change the 3 here CHECKSUM(NEWID()) FROM master.dbo.spt_values AS SV; GO -- Show physical index statistics -- to confirm the number of pages SELECT DDIPS.index_type_desc, DDIPS.alloc_unit_type_desc, DDIPS.page_count, DDIPS.record_count, DDIPS.avg_record_size_in_bytes FROM sys.dm_db_index_physical_stats ( DB_ID(), OBJECT_ID(N'dbo.Test', N'U'), 1, -- Index ID NULL, -- Partition ID 'DETAILED' ) AS DDIPS WHERE DDIPS.index_level = 0; -- leaf level only GO -- Clear the plan cache DBCC FREEPROCCACHE; GO -- Clear the log CHECKPOINT; GO -- Main test INSERT dbo.Test (c1) SELECT TOP (269) 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_CLUSTERED' AND FD.AllocUnitName = N'dbo.Test.PK dbo.Test (id)'; GO
クラスタ化されたインデックスに3ページがプリロードされている場合 、テスト挿入は完全にログに記録されます (簡潔にするためにトランザクションログの詳細レコードは省略されています):
テーブルに1ページまたは2ページのみがプリロードされている場合 、テスト挿入物は最小限ログに記録されます :
テーブルがプリロードされていない場合 どのページでも、テストはパート2の空のクラスター化テーブルのデモを実行するのと同じですが、なしです。 TABLOCK
ヒント:
最初の130行は完全にログに記録されます 。これは、開始する前はインデックスが空であり、最初のページに130行が収まっているためです。 FastLoadContext
の場合、最初のページは常に完全にログに記録されることを忘れないでください が使用され、インデックスは事前に空でした。残りの139行は、最小限のログで挿入されます 。
TABLOCK
の場合 ヒントが挿入に追加されます。すべてのページ 空のクラスター化インデックスのロードがRowsetBulk
の対象となるため、ログは最小限に抑えられます(最初のログを含む)。 メカニズム( Sch-M
を取ることを犠牲にして ロック)。
2。 FDemandRowsSortedForPerformance
FOptimizeInsert
の場合 テストは失敗します、 DMLRequestSort
まだtrueに設定されている可能性があります sqllang!CUpdUtil ::FDemandRowsSortedForPerformance
の2番目のテストセット コード。これらの条件はもう少し複雑なので、いくつかのパラメーターを定義すると便利です。
-
P
–既存のリーフレベルのページの数 ターゲットインデックス 。 私
–推定 挿入する行数。-
R
=P
/私コード> (挿入された行ごとのターゲットページ)。
-
T
–ターゲットパーティションの数(パーティション化されていない場合は1)。
DMLRequestSort
の値を決定するロジック その場合:
-
P <=16
の場合 falseを返します 、それ以外の場合 :-
R <8
の場合 :-
P> 524
の場合 trueを返す 、それ以外の場合は false 。
-
-
R> =8
の場合 :-
T> 1
の場合 およびI>250
trueを返す 、それ以外の場合は false 。
-
-
上記のテストは、プランのコンパイル中にクエリプロセッサによって評価されます。 最終条件があります ストレージエンジンコードによって評価されます( IndexDataSetSession ::WakeUpInternal
)実行時:
-
DMLRequestSort
現在true; および -
I> =100
。
次に、このすべてのロジックを管理可能な部分に分解します。
16を超える既存のターゲットページ
最初のテストP<=16
つまり、既存のリーフページが17未満のインデックスは、 FastLoadContext
の対象にはなりません。 このコードパスを介して。この点を完全に明確にするために、 P
前のターゲットインデックス内のリーフレベルのページ数です。 INSERT ... SELECT
実行されます。
ロジックのこの部分を示すために、テストクラスターテーブルに16ページをプリロードします。 データの。これには2つの重要な効果があります(両方のコードパスが falseを返す必要があることに注意してください 最終的にfalse DMLRequestSort
の値 ):
- 前の
FOptimizeInsert
を確実にします テスト失敗 、3番目の条件が満たされていないため(P <3
。 -
P <=16
FDemandRowsSortedForPerformance
の条件 また、しない 満たされる。
したがって、 FastLoadContext
が必要です。 有効にしないでください。変更されたデモスクリプトは次のとおりです。
IF OBJECT_ID(N'dbo.Test', N'U') IS NOT NULL BEGIN DROP TABLE dbo.Test; END; GO CREATE TABLE dbo.Test ( id integer NOT NULL IDENTITY CONSTRAINT [PK dbo.Test (id)] PRIMARY KEY, c1 integer NOT NULL, padding char(45) NOT NULL DEFAULT '' ); GO -- 130 rows per page for this table -- structure with row versioning off INSERT dbo.Test (c1) SELECT TOP (16 * 130) -- 16 pages CHECKSUM(NEWID()) FROM master.dbo.spt_values AS SV; GO -- Show physical index statistics -- to confirm the number of pages SELECT DDIPS.index_type_desc, DDIPS.alloc_unit_type_desc, DDIPS.page_count, DDIPS.record_count, DDIPS.avg_record_size_in_bytes FROM sys.dm_db_index_physical_stats ( DB_ID(), OBJECT_ID(N'dbo.Test', N'U'), 1, -- Index ID NULL, -- Partition ID 'DETAILED' ) AS DDIPS WHERE DDIPS.index_level = 0; -- leaf level only GO -- Clear the plan cache DBCC FREEPROCCACHE; GO -- Clear the log CHECKPOINT; GO -- Main test INSERT dbo.Test (c1) SELECT TOP (269) CHECKSUM(NEWID()) FROM master.dbo.spt_values AS SV1 CROSS JOIN master.dbo.spt_values AS SV2; 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_CLUSTERED' AND FD.AllocUnitName = N'dbo.Test.PK dbo.Test (id)';
269行すべてが完全にログに記録されます 予測どおり:
挿入する新しい行の数をいくら高く設定しても、上記のスクリプトは決してないことに注意してください。 最小限のロギングを生成する P <=16
のため テスト(および P <3
FOptimizeInsert
でテストする 。
多数の行を使用してデモを自分で実行することを選択した場合は、個々のトランザクションログレコードを表示するセクションをコメントアウトしてください。コメントアウトしないと、非常に長い時間待機し、SSMSがクラッシュする可能性があります。 (公平を期すために、とにかくそうするかもしれませんが、なぜリスクを増すのですか。)
17以上がある場合 既存のインデックスのリーフページ、前の P <=16
テストは失敗しません。ロジックの次のセクションでは、既存のページの比率を扱います。 新しく挿入された行 。 最小限のロギングを達成するには、これも合格する必要があります 。注意点として、関連する条件は次のとおりです。
- 比率
R
=P
/私コード> 。
-
R <8
の場合 :-
P> 524
の場合 trueを返す 、それ以外の場合は false 。
-
また、少なくとも100行の最終的なストレージエンジンテストを覚えておく必要があります。
-
I> =100
。
これらの条件を少し再編成し、すべて 次のうち、真でなければなりません:
-
P> 524
(既存のインデックスページ) -
I> =100
(挿入された行の推定値) -
P / I <8
(比率R
)
これらの3つの条件を満たすには複数の方法があります。 P
の可能な最小値を選択しましょう (525)および I
(100) R
を与える (525/100)=5.25の値。これは( R <8
テスト)、したがって、この組み合わせにより、最小限のロギングが得られると予想されます。 :
IF OBJECT_ID(N'dbo.Test', N'U') IS NOT NULL BEGIN DROP TABLE dbo.Test; END; GO CREATE TABLE dbo.Test ( id integer NOT NULL IDENTITY CONSTRAINT [PK dbo.Test (id)] PRIMARY KEY, c1 integer NOT NULL, padding char(45) NOT NULL DEFAULT '' ); GO -- 130 rows per page for this table -- structure with row versioning off INSERT dbo.Test (c1) SELECT TOP (525 * 130) -- 525 pages CHECKSUM(NEWID()) FROM master.dbo.spt_values AS SV1 CROSS JOIN master.dbo.spt_values AS SV2; GO -- Show physical index statistics -- to confirm the number of pages SELECT DDIPS.index_type_desc, DDIPS.alloc_unit_type_desc, DDIPS.page_count, DDIPS.record_count, DDIPS.avg_record_size_in_bytes FROM sys.dm_db_index_physical_stats ( DB_ID(), OBJECT_ID(N'dbo.Test', N'U'), 1, -- Index ID NULL, -- Partition ID 'DETAILED' ) AS DDIPS WHERE DDIPS.index_level = 0; -- leaf level only GO -- Clear the plan cache DBCC FREEPROCCACHE; GO -- Clear the log CHECKPOINT; GO -- Main test INSERT dbo.Test (c1) SELECT TOP (100) CHECKSUM(NEWID()) FROM master.dbo.spt_values AS SV1 CROSS JOIN master.dbo.spt_values AS SV2; 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_CLUSTERED' AND FD.AllocUnitName = N'dbo.Test.PK dbo.Test (id)';
100行のINSERT... SELECT
確かに最小限のログ :
推定を減らす 行を99に挿入しました( I> =100
を壊します )、および/または既存のインデックスページの数を524に減らす( P> 524
を壊す) )結果は完全なログ記録になります 。 R
のように変更することもできます 完全なログ記録を生成するために8以上である 。たとえば、 P =1000
を設定します およびI=125
R =8
を与えます 、次の結果が得られます:
挿入された125行は完全にログに記録されました 予想通り。 (これは、インデックスが事前に空ではなかったため、最初のページの完全なログ記録によるものではありません。)
上記のすべてのテストが失敗した場合、残りの1つのテストには R> =8
が必要です。 のみ パーティションの数( T
)が1より大きいおよび 250以上の推定があります 挿入された行( I
)。思い出してください:
-
R> =8
の場合 :-
T> 1
の場合 およびI>250
trueを返す 、それ以外の場合は false 。
-
1つの微妙な点:パーティション化の場合 インデックス、すべての最初のページの行が完全にログに記録されるというルール(最初は空のインデックスの場合)は、パーティションごとに適用されます。 。 15,000のパーティションを持つオブジェクトの場合、これは15,000の完全にログに記録された「最初の」ページを意味します。
本文に記載されている式と評価の順序は、デバッガーを使用したコード検査に基づいています。それらは、実際のコードで使用されるタイミングと順序を厳密に表す形式で提示されました。
これらの条件を少し並べ替えて単純化して、最小限のロギングの実際的な要件のより簡潔な要約を作成することができます。 INSERT ... SELECT
を使用してBツリーに挿入する場合 。以下の洗練された式は、次の3つのパラメーターを使用します。
-
P
=既存の数 リーフレベルのページにインデックスを付けます。 私
=推定 挿入する行数。-
S
=推定 データサイズを8KBページに挿入します。
-
sqlmin!RowsetBulk
を使用します 。 - 空が必要です
TABLOCK
を使用したクラスター化インデックスターゲット (または同等のもの)。 -
DMLRequestSort =true
が必要です Clustered Index Insert オペレーター。 -
DMLRequestSort
true
に設定されていますI> 250
の場合 およびS>2
。 - 挿入されたすべての行は最小限にログに記録されます 。
-
Sch-M
ロックはテーブルへの同時アクセスを防ぎます。
-
sqlmin!FastLoadContext
を使用します 。 - 最小限のログを有効にします bツリーインデックスへの挿入:
- クラスター化または非クラスター化。
- テーブルロックの有無にかかわらず。
- ターゲットインデックスが空かどうか。
-
DMLRequestSort =true
が必要です 関連するインデックス挿入 プランオペレーター。 - ブランドの新しいページに書き込まれた行のみ 一括読み込みされ、最小限のログ 。
- 最初のページ 以前の空のインデックスの パーティションは常に完全にログに記録されます 。
-
I> =100
の絶対最小値 。 - SQLServer2016の前にトレースフラグ610が必要です。
- SQL Server 2016からデフォルトで利用可能です(トレースフラグ692は無効になります)。
DMLRequestSort
true
に設定されています 対象:
- 任意のインデックス (パーティション化されているかどうか)if:
-
I> 250
およびP<3
およびS>2
; または -
I> =100
およびP>524
およびP
-
パーティションインデックスのみ (> 1パーティション)、 DMLRequestSort
true
も設定されます 場合:
-
I> 250
およびP>16
およびP>=I * 8
これらのFastLoadContext
から生じるいくつかの興味深いケースがあります 条件:
- すべて パーティション化されていないに挿入します 3から524の間のインデックス (包括的)既存のリーフページは完全にログに記録されます 追加された行の数と合計サイズに関係なく。これは、小さな(ただし空ではない)テーブルへの大きな挿入に最も顕著に影響します。
- すべて パーティション化されたに挿入します 3から16のインデックス 既存のページは完全にログに記録されます 。
- 大きなインサート 大きなパーティション化されていない インデックスは最小限にログに記録されることはできません 不等式のため
P 。
P
の場合 大きい、それに応じて大きい推定 挿入された行の数(I
) 必要とされている。たとえば、800万ページのインデックスは、最小限のロギングをサポートできません。 100万行以下を挿入する場合。
デモのクラスター化インデックスに適用されるのと同じ考慮事項と計算が、非クラスター化にも適用されます。 インデックスが専用のプランオペレーターによって維持されている限り、bツリーインデックスも同様です。 (ワイド 、またはインデックスごと 予定)。ベーステーブルオペレーターによって維持される非クラスター化インデックス(例:クラスター化インデックス挿入 ) FastLoadContext
の対象ではありません 。
数式パラメータは、非クラスタ化ごとに新たに評価する必要があることに注意してください。 インデックス演算子—計算された行サイズ、既存のインデックスページの数、およびカーディナリティの見積もり。
低カーディナリティ推定値に注意してください インデックス挿入で 演算子、これらは I
に影響するため およびS
パラメーター。カーディナリティ推定エラーが原因でしきい値に達しない場合、挿入は完全にログに記録されます 。
DMLRequestSort
プランとともにキャッシュされます —再利用された計画の実行ごとに評価されません。これにより、よく知られているパラメータ感度問題(「パラメータスニッフィング」とも呼ばれます)の形式が導入される可能性があります。
P
の値 (インデックスリーフページ)は更新されません すべてのステートメントの開始時に。現在の実装では、バッチ全体の値がキャッシュされます。 。これは予期しない副作用を引き起こす可能性があります。たとえば、 TRUNCATE TABLE
同じバッチ INSERT ... SELECT
として P
はリセットされません この記事で説明されている計算ではゼロになります—切り捨て前の値を引き続き使用するため、再コンパイルは役に立ちません。回避策は、大きな変更を別々のバッチで送信することです。
FDemandRowsSortedForPerformance
を強制することができます trueを返す 文書化されていない、サポートされていないを設定する データを変更するT-SQLクエリの最適化で書いたように、フラグ2332をトレースします。 TF 2332がアクティブな場合、挿入する推定行数 それでも少なくとも100である必要があります 。 TF2332は最小限のロギングに影響します FastLoadContext
の決定 のみ( DMLRequestSort
までは、パーティション化されたヒープに対して有効です。 懸念されますが、 FastLoadContext
であるため、ヒープ自体には影響しません。 インデックスにのみ適用されます)。
ワイド/インデックスごと 非クラスター化インデックス保守の計画形状は、トレースフラグ8790を使用して行ストアテーブルに強制できます(公式には文書化されていませんが、ナレッジベースの記事および上記のTF2332にリンクされている私の記事で言及されています)。
すべてSQLServerチームのSunilAgarwalによる:
- 一括インポートの最適化とは何ですか?
- 一括インポートの最適化(最小限のロギング)
- SQLServer2008での最小限のログ変更
- SQL Server 2008での最小限のログの変更(パート2)
- SQL Server 2008での最小限のログの変更(パート3)