インデックス付きテーブルに挿入するときに最小限のログが取得されないのはなぜですか


14

私はさまざまなシナリオで最小限のロギング挿入をテストしており、TABLOCKを使用して非クラスター化インデックスを持つヒープにINSERT INTO SELECTを読んだことから、SQL Server 2016+は最小限に記録する必要がありますが、これを行うときは完全なロギング。私のデータベースは単純な復旧モデルであり、インデックスとTABLOCKを使用せずに、ヒープ上に最小限のログが挿入されます。

Stack Overflowデータベースの古いバックアップを使用してテストし、次のスキーマでPostsテーブルの複製を作成しました...

CREATE TABLE [dbo].[PostsDestination](
    [Id] [int] NOT NULL,
    [AcceptedAnswerId] [int] NULL,
    [AnswerCount] [int] NULL,
    [Body] [nvarchar](max) NOT NULL,
    [ClosedDate] [datetime] NULL,
    [CommentCount] [int] NULL,
    [CommunityOwnedDate] [datetime] NULL,
    [CreationDate] [datetime] NOT NULL,
    [FavoriteCount] [int] NULL,
    [LastActivityDate] [datetime] NOT NULL,
    [LastEditDate] [datetime] NULL,
    [LastEditorDisplayName] [nvarchar](40) NULL,
    [LastEditorUserId] [int] NULL,
    [OwnerUserId] [int] NULL,
    [ParentId] [int] NULL,
    [PostTypeId] [int] NOT NULL,
    [Score] [int] NOT NULL,
    [Tags] [nvarchar](150) NULL,
    [Title] [nvarchar](250) NULL,
    [ViewCount] [int] NOT NULL
)
CREATE NONCLUSTERED INDEX ndx_PostsDestination_Id ON PostsDestination(Id)

次に、postsテーブルをこのテーブルにコピーしようとします...

INSERT INTO PostsDestination WITH(TABLOCK)
SELECT * FROM Posts ORDER BY Id 

fn_dblogとログファイルの使用状況を見ると、これから最小限のログを取得していないことがわかります。2016年以前のバージョンでは、インデックス付きテーブルに最小限のログを記録するためにトレースフラグ610が必要であり、これを設定しようとしましたが、まだ喜びはありませんでした。

私はここで何かを見逃していると思いますか?

編集-詳細

さらに情報を追加するために、最小限のログを検出しようとするために書いた次の手順を使用していますが、ここで何か間違っている可能性があります...

/*
    Example Usage...

    EXEC sp_GetLogUseStats
   @Sql = '
      INSERT INTO PostsDestination
      SELECT TOP 500000 * FROM Posts ORDER BY Id ',
   @Schema = 'dbo',
   @Table = 'PostsDestination',
   @ClearData = 1

*/

CREATE PROCEDURE [dbo].[sp_GetLogUseStats]
(   
   @Sql NVARCHAR(400),
   @Schema NVARCHAR(20),
   @Table NVARCHAR(200),
   @ClearData BIT = 0
)
AS

IF @ClearData = 1
   BEGIN
   TRUNCATE TABLE PostsDestination
   END

/*Checkpoint to clear log (Assuming Simple/Bulk Recovery Model*/
CHECKPOINT  

/*Snapshot of logsize before query*/
CREATE TABLE #BeforeLogUsed(
   [Db] NVARCHAR(100),
   LogSize NVARCHAR(30),
   Used NVARCHAR(50),
   Status INT
)
INSERT INTO #BeforeLogUsed
EXEC('DBCC SQLPERF(logspace)')

/*Run Query*/
EXECUTE sp_executesql @SQL

/*Snapshot of logsize after query*/
CREATE TABLE #AfterLLogUsed(    
   [Db] NVARCHAR(100),
   LogSize NVARCHAR(30),
   Used NVARCHAR(50),
   Status INT
)
INSERT INTO #AfterLLogUsed
EXEC('DBCC SQLPERF(logspace)')

/*Return before and after log size*/
SELECT 
   CAST(#AfterLLogUsed.Used AS DECIMAL(12,4)) - CAST(#BeforeLogUsed.Used AS DECIMAL(12,4)) AS LogSpaceUsersByInsert
FROM 
   #BeforeLogUsed 
   LEFT JOIN #AfterLLogUsed ON #AfterLLogUsed.Db = #BeforeLogUsed.Db
WHERE 
   #BeforeLogUsed.Db = DB_NAME()

/*Get list of affected indexes from insert query*/
SELECT 
   @Schema + '.' + so.name + '.' +  si.name AS IndexName
INTO 
   #IndexNames
FROM 
   sys.indexes si 
   JOIN sys.objects so ON si.[object_id] = so.[object_id]
WHERE 
   si.name IS NOT NULL
   AND so.name = @Table
/*Insert Record For Heap*/
INSERT INTO #IndexNames VALUES(@Schema + '.' + @Table)

/*Get log recrod sizes for heap and/or any indexes*/
SELECT 
   AllocUnitName,
   [operation], 
   AVG([log record length]) AvgLogLength,
   SUM([log record length]) TotalLogLength,
   COUNT(*) Count
INTO #LogBreakdown
FROM 
   fn_dblog(null, null) fn
   INNER JOIN #IndexNames ON #IndexNames.IndexName = allocunitname
GROUP BY 
   [Operation], AllocUnitName
ORDER BY AllocUnitName, operation

SELECT * FROM #LogBreakdown
SELECT AllocUnitName, SUM(TotalLogLength)  TotalLogRecordLength 
FROM #LogBreakdown
GROUP BY AllocUnitName

次のコードを使用して、インデックスとTABLOCKなしでヒープに挿入しています...

EXEC sp_GetLogUseStats
   @Sql = '
      INSERT INTO PostsDestination
      SELECT * FROM Posts ORDER BY Id ',
   @Schema = 'dbo',
   @Table = 'PostsDestination',
   @ClearData = 1

これらの結果が得られます

ここに画像の説明を入力してください

0.0024MBのログファイルの増加、非常に小さいログレコードサイズ、およびそれらのごくわずかで、これが最小のログを使用していることを嬉しく思います。

次に、idに非クラスター化インデックスを作成すると...

CREATE INDEX ndx_PostsDestination_Id ON PostsDestination(Id)

次に、同じ挿入をもう一度実行します...

ここに画像の説明を入力してください

非クラスター化インデックスで最小限のログが取得されないだけでなく、ヒープでもログが失われました。いくつかのテストを行った後、IDをクラスター化するとログは最小限になりますが、2016 +を読んだところから、tablockを使用すると、クラスター化されていないインデックスを持つヒープに最小限にログが記録されるはずです。

最終編集

SQL Server UserVoiceで Microsoftに動作を報告しましたが、応答があれば更新します。また、https://gavindraper.com/2018/05/29/SQL-Server-Minimal-Logging-Inserts/で作業できなかった最小ログシナリオの詳細をすべて書きました


回答:


12

Stack Overflow 2010データベースを使用してSQL Server 2017で結果を再現することはできますが、結論(すべて)ではありません。

最小限のログへのヒープが使用する場合に利用できないINSERT...SELECTTABLOCKいる非クラスタ化インデックスとヒープへの予期しません。私の推測では、(b)と同時に(ヒープ)をINSERT...SELECT使用したバルクロードをサポートすることはできません。これがバグであるか、仕様であるかを確認できるのはマイクロソフトのみです。RowsetBulkFastLoadContext

ヒープの非クラスター化インデックスは最小限ログに記録されます(TF610がオンになっている、またはSQL Server 2016+が使用され、有効になっている場合FastLoadContext)、次の警告があります。

  • 新しく割り当てられたページに挿入された行のみが最小限のログに記録されます。
  • 操作の開始時にインデックスが空だった場合、最初のインデックスページに追加された行は最小限ログに記録されません。

LOP_INSERT_ROWS非クラスター化インデックスに表示される497 エントリは、インデックスの最初のページに対応しています。インデックスは事前に空だったため、これらの行は完全にログに記録されます。残りの行はすべて最小限に記録されます。文書化されたトレースフラグ692が有効(2016+)で無効FastLoadContextになっている場合、すべての非クラスター化インデックス行は最小限に記録されます。


ファイルから同じテーブル(インデックス付き)を一括読み込みすると、ヒープと非クラスター化インデックスの両方に最小限のログが適用されることわかりました。BULK INSERT

BULK INSERT dbo.PostsDestination
FROM 'D:\SQL Server\Posts.bcp'
WITH (TABLOCK, DATAFILETYPE = 'native');

完全を期すためにこれに注意します。をINSERT...SELECT使用したバルクロードでは異なるコードパスが使用されるため、動作が異なるという事実はまったく予想外ではありません。


以下のための完全な詳細使用して、最小限のロギングのRowsetBulkFastLoadContextINSERT...SELECTSQLPerformance.comに私の3部作を参照してください。

  1. ヒープテーブルへのINSERT…SELECTを使用した最小限のロギング
  2. 空のクラスターテーブルへのINSERT…SELECTを使用した最小限のロギング
  3. INSERT…SELECTおよびFast Load Contextを使用した最小限のロギング

ブログ投稿のその他のシナリオ

コメントは閉じられているので、ここで簡単に説明します。

トレース610または2016+の空のクラスター化インデックス

これはを使用FastLoadContextせずに最小限記録されますTABLOCK。完全にログに記録されるのは、トランザクションの開始時にクラスター化インデックスが空だったため、最初のページに挿入された行のみです。

データとトレース610 OR 2016+を使用したクラスター化インデックス

これもを使用して最小限に記録されますFastLoadContext。既存のページに追加された行は完全に記録され、残りは最小限に記録されます。

非クラスター化インデックスとTABLOCKまたはTrace 610 / SQL 2016+を使用したクラスター化インデックス

これはFastLoadContext、非クラスター化インデックスが別のオペレーターによって維持され、DMLRequestSorttrueに設定され、投稿に記載されている他の条件が満たされている限り、最小限に記録することもできます。


2

以下のドキュメントは古いですが、それでも優れた読み物です。

SQL 2016では、トレースフラグ610とALLOW_PAGE_LOCKSはデフォルトでオンになっていますが、誰かがそれらを無効にしている可能性があります。

データロードパフォーマンスガイド

(3)オプティマイザーによって選択されたプランに応じて、テーブルの非クラスター化インデックスは完全に記録される場合と最小限に記録される場合があります。

SELECTステートメントは、TOPとORDER BYを持っているため問題になる可能性があります。インデックスとは異なる順序でテーブルにデータを挿入しているため、SQLはバックグラウンドで多くのソートを実行している可能性があります。

更新2

実際に最小限のログを取得している可能性があります。TraceFlag 610をオンにすると、ログの動作が異なり、SQLは、問題が発生した場合にロールバックを実行するために十分なスペースをログに確保しますが、実際にはログを使用しません。

これはおそらく予約済み(未使用)スペースをカウントしています

EXEC('DBCC SQLPERF(logspace)')

このコードは使用済みから予約済みに分割されます

SELECT
    database_transaction_log_bytes_used
    ,database_transaction_log_bytes_reserved
    ,*
FROM sys.dm_tran_database_transactions 
WHERE database_id = DB_ID()

最小限のログ記録(Microsoftに関する限り)は、実際にはログの最小量のIOを実行することであり、ログの量は予約されていないと考えています。

このリンクをご覧ください

更新1

TABLOCKの代わりにTABLOCKXを使用してみてください。Tablockを使用すると、まだ共有ロックが保持されているため、別のプロセスが開始された場合にSQLがログを記録する可能性があります。

TABLOCKは、HOLDLOCKと組み合わせて使用​​する必要がある場合があります。これにより、トランザクションが終了するまでTablockが強制されます。

また、ソーステーブルにロックを設定します[投稿]。トランザクションの実行中にソーステーブルが変更される可能性があるため、ロギングが行われている可能性があります。ソースがSQLテーブルではない場合、Paul Whiteは最小限のロギングを実現しました。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.