Warum erhalte ich beim Einfügen in indizierte Tabellen keine minimale Protokollierung?


13

Ich teste minimale Protokolleinfügungen in verschiedenen Szenarien und von dem, was ich unter Verwendung von TABLOCK und SQL Server 2016+ als INSERT INTO SELECT in einen Heap mit einem nicht gruppierten Index gelesen habe, sollte nur eine minimale Protokollierung erfolgen vollständige Protokollierung. Meine Datenbank befindet sich im einfachen Wiederherstellungsmodell und ich erhalte erfolgreich minimal protokollierte Einfügungen auf einem Heap ohne Indizes und TABLOCK.

Ich verwende eine alte Sicherung der Stack Overflow-Datenbank zum Testen und habe ein Replikat der Posts-Tabelle mit dem folgenden Schema erstellt ...

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)

Ich versuche dann, die Pfostentabelle in diese Tabelle zu kopieren ...

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

Wenn ich mir fn_dblog und die Verwendung der Protokolldatei ansehe, sehe ich, dass ich dadurch keine minimale Protokollierung erhalte. Ich habe gelesen, dass für Versionen vor 2016 das Ablaufverfolgungsflag 610 erforderlich ist, um sich minimal in indizierten Tabellen zu protokollieren. Ich habe auch versucht, dies festzulegen, aber immer noch keine Freude.

Vermutlich fehlt mir hier etwas?

EDIT - Mehr Info

Um weitere Informationen hinzuzufügen, benutze ich das folgende Verfahren, das ich geschrieben habe, um zu versuchen, minimale Protokollierung zu erkennen. Vielleicht stimmt hier etwas nicht ...

/*
    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

Einfügen in einen Heap ohne Indizes und TABLOCK mit folgendem Code ...

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

Ich bekomme diese Ergebnisse

Bildbeschreibung hier eingeben

Bei 0,0024 MB Protokolldatei-Wachstum, sehr kleinen Protokolldatensatzgrößen und sehr wenigen davon bin ich froh, dass hier nur minimale Protokollierung verwendet wird.

Wenn ich dann einen nicht geclusterten Index für id ...

CREATE INDEX ndx_PostsDestination_Id ON PostsDestination(Id)

Dann starte nochmal meinen selben Einsatz ...

Bildbeschreibung hier eingeben

Ich erhalte nicht nur keine minimale Protokollierung für den nicht gruppierten Index, sondern habe ihn auch auf dem Heap verloren. Nachdem ich einige weitere Tests durchgeführt habe, scheint es, dass wenn ich ID geclustert habe, es minimal protokolliert, aber von dem, was ich 2016+ gelesen habe, sollte es minimal auf einem Heap mit nicht geclustertem Index protokolliert werden, wenn Tablock verwendet wird.

FINAL EDIT :

Ich habe Microsoft das Verhalten in SQL Server UserVoice gemeldet und werde aktualisiert, wenn ich eine Antwort erhalte. Ich habe auch die vollständigen Details der minimalen Protokollszenarien aufgeschrieben, mit denen ich unter https://gavindraper.com/2018/05/29/SQL-Server-Minimal-Logging-Inserts/ nicht arbeiten konnte.


3
Paul White hat hier eine hilfreiche Antwort .
Erik Darling

Antworten:


11

Ich kann Ihre Ergebnisse in SQL Server 2017 mithilfe der Stack Overflow 2010-Datenbank reproduzieren, aber nicht alle Ihre Schlussfolgerungen.

Minimale Protokollierung auf den Heap ist nicht verfügbar bei der Verwendung INSERT...SELECTmit TABLOCKzu einem Haufen mit einem nicht gruppierten Index, der ist unerwartet . Ich vermute, dass INSERT...SELECTMassenladungen nicht gleichzeitig mit RowsetBulk(heap) und FastLoadContext(b-tree) unterstützt werden können. Nur Microsoft kann bestätigen, ob es sich um einen Fehler handelt oder ob dies beabsichtigt ist.

Der nicht gruppierte Index auf dem Heap wirdFastLoadContext mit den folgenden Einschränkungen minimal protokolliert (vorausgesetzt, TF610 ist aktiviert oder SQL Server 2016+ wird verwendet ):

  • Nur Zeilen, die in neu zugewiesene Seiten eingefügt wurden, werden minimal protokolliert.
  • Der ersten Indexseite hinzugefügte Zeilen werden nicht minimal protokolliert, wenn der Index zu Beginn des Vorgangs leer war.

Die 497 LOP_INSERT_ROWSangezeigten Einträge für den Nonclustered-Index entsprechen der ersten Seite des Index. Da der Index zuvor leer war, werden diese Zeilen vollständig protokolliert. Die restlichen Zeilen werden alle minimal protokolliert . Wenn das Flag 692 für dokumentierte Ablaufverfolgung zum Deaktivieren aktiviert ist (2016+) FastLoadContext, werden alle nicht gruppierten Indexzeilen minimal protokolliert.


Ich habe festgestellt, dass die minimale Protokollierung sowohl auf den Heap- als auch auf den Nonclustered-Index angewendet wird , wenn dieselbe Tabelle (mit Index) aus einer Datei als Bulk geladen wird :BULK INSERT

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

Ich stelle dies der Vollständigkeit halber fest. Beim Massenladen werden INSERT...SELECTunterschiedliche Codepfade verwendet, sodass das unterschiedliche Verhalten nicht völlig unerwartet ist.


Für weitere Informationen über eine minimale Protokollierung verwenden RowsetBulkund FastLoadContextmit INSERT...SELECTmeiner dreiteiligen Serie auf SQLPerformance.com sehen:

  1. Minimale Protokollierung mit INSERT… SELECT in Heap-Tabellen
  2. Minimale Protokollierung mit INSERT… SELECT in leere gruppierte Tabellen
  3. Minimale Protokollierung mit INSERT… SELECT und Fast Load Context

Andere Szenarien aus Ihrem Blogbeitrag

Die Kommentare sind geschlossen, daher werde ich hier kurz darauf eingehen.

Leerer gruppierter Index mit Spur 610 oder 2016+

Dies wird minimal mit FastLoadContextwithout protokolliert TABLOCK. Die einzigen Zeilen, die vollständig protokolliert wurden, sind die Zeilen, die auf der ersten Seite eingefügt wurden, da der Clustered-Index zu Beginn der Transaktion leer war.

Clustered Index mit Daten und Trace 610 OR 2016+

Dies wird auch minimal mit protokolliert FastLoadContext. Der vorhandenen Seite hinzugefügte Zeilen werden vollständig protokolliert, der Rest wird minimal protokolliert.

Clustered-Index mit NonClustered-Indizes und TABLOCK oder Trace 610 / SQL 2016+

Dies kann auch mit minimal protokolliert werden FastLoadContext, solange der nicht gruppierte Index von einem separaten Operator verwaltet DMLRequestSortwird, auf true festgelegt ist und die anderen in meinen Posts festgelegten Bedingungen erfüllt sind.


2

Das folgende Dokument ist alt, aber immer noch eine ausgezeichnete Lektüre.

In SQL 2016 sind das Ablaufverfolgungsflag 610 und ALLOW_PAGE_LOCKS standardmäßig aktiviert, aber möglicherweise wurden sie von jemandem deaktiviert.

Leistungshandbuch für das Laden von Daten

(3) Abhängig vom vom Optimierer gewählten Plan kann der nicht gruppierte Index in der Tabelle entweder vollständig oder minimal protokolliert werden.

Die SELECT-Anweisung kann das Problem sein, da Sie ein TOP und ein ORDER BY haben. Sie fügen Daten in einer anderen Reihenfolge als im Index in die Tabelle ein, sodass SQL möglicherweise viel im Hintergrund sortiert.

UPDATE 2

Möglicherweise erhalten Sie tatsächlich eine minimale Protokollierung. Wenn TraceFlag 610 ON aktiviert ist, verhält sich das Protokoll anders. SQL reserviert genügend Speicherplatz im Protokoll, um ein Rollback durchzuführen, wenn Probleme auftreten. Das Protokoll wird jedoch nicht verwendet.

Dies zählt wahrscheinlich den reservierten (nicht genutzten) Speicherplatz

EXEC('DBCC SQLPERF(logspace)')

Dieser Code teilt Reserviert von Verwendet auf

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

Ich nehme an, bei der minimalen Protokollierung (soweit es Microsoft betrifft) geht es tatsächlich darum, die geringste Anzahl von E / A-Vorgängen für das Protokoll auszuführen, und nicht darum, wie viel des Protokolls reserviert ist.

Schauen Sie sich diesen Link an .

UPDATE 1

Versuchen Sie es mit TABLOCKX anstelle von TABLOCK. Bei Tablock haben Sie immer noch eine gemeinsame Sperre, sodass sich SQL möglicherweise anmeldet, falls ein anderer Prozess gestartet wird.

TABLOCK muss möglicherweise in Verbindung mit HOLDLOCK verwendet werden. Dies erzwingt das Tablock bis zum Ende Ihrer Transaktion.

Setzen Sie auch eine Sperre für die Quelltabelle [Posts], da die Quelltabelle möglicherweise während Ihrer Transaktion geändert wird. Paul White erreichte eine minimale Protokollierung, wenn die Quelle keine SQL-Tabelle war.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.