Database
 sql >> Datenbank >  >> RDS >> Database

Minimale Protokollierung mit INSERT…SELECT und Fast Load Context

Dieser Beitrag enthält neue Informationen über die Voraussetzungen für minimal protokolliertes Massenladen bei Verwendung von INSERT...SELECT in indizierte Tabellen .

Die interne Einrichtung, die diese Fälle ermöglicht, heißt FastLoadContext . Es kann von SQL Server 2008 bis einschließlich 2014 mithilfe des dokumentierten Ablaufverfolgungsflags 610 aktiviert werden. Ab SQL Server 2016 FastLoadContext ist standardmäßig aktiviert; das Trace-Flag ist nicht erforderlich.

Ohne FastLoadContext , die einzigen Indexeinfügungen, die minimal protokolliert werden können sind die in ein leeres gruppierter Index ohne Sekundärindizes, wie in Teil 2 dieser Serie behandelt. Die minimale Protokollierung Bedingungen für nicht indizierte Heap-Tabellen wurden in Teil eins behandelt.

Weitere Hintergrundinformationen finden Sie im Data Performance Loading Guide und beim Tiger Team Hinweise zu den Verhaltensänderungen für SQL Server 2016.

Kontext für schnelles Laden

Zur Erinnerung:RowsetBulk -Einrichtung (in Teil 1 und 2 behandelt) aktiviert minimal protokolliert Massenladen für:

  • Leerer und nicht leerer Haufen Tabellen mit:
    • Tabellensperre; und
    • Keine sekundären Indizes.
  • Leere geclusterte Tabellen , mit:
    • Tabellensperre; und
    • Keine sekundären Indizes; und
    • DMLRequestSort=true auf der Clustered Index Insert Betreiber.

Der FastLoadContext Codepfad fügt Unterstützung für minimal protokolliert hinzu und gleichzeitig Massenladen am:

  • Leer und nicht leer geclustert B-Tree-Indizes.
  • Leer und nicht leer nicht gruppiert B-Tree-Indizes, die von einem dedizierten verwaltet werden Index einfügen Planbetreiber.

Der FastLoadContext erfordert auch DMLRequestSort=true in jedem Fall beim entsprechenden Tarifbetreiber.

Möglicherweise haben Sie eine Überschneidung zwischen RowsetBulk bemerkt und FastLoadContext für leere gruppierte Tabellen ohne Sekundärindizes. Ein TABLOCK Hinweis ist nicht erforderlich mit FastLoadContext , muss aber nicht fehlen entweder. Als Konsequenz eine passende Einfügung mit TABLOCK kann immer noch für minimale Protokollierung in Frage kommen über FastLoadContext wenn es fehlschlägt, den detaillierten RowsetBulk Tests.

FastLoadContext kann deaktiviert werden auf SQL Server 2016 unter Verwendung des dokumentierten Ablaufverfolgungsflags 692. Das erweiterte Ereignis des Debug-Kanals fastloadcontext_enabled kann verwendet werden, um FastLoadContext zu überwachen Nutzung pro Indexpartition (Rowset). Dieses Ereignis wird nicht für RowsetBulk ausgelöst lädt.

Gemischte Protokollierung

Ein einzelnes INSERT...SELECT Anweisung mit FastLoadContext kann sich vollständig anmelden einige Zeilen bei minimaler Protokollierung andere.

Zeilen werden eine nach der anderen eingefügt durch die Indexeinlage Bediener und vollständig protokolliert in den folgenden Fällen:

  • Alle Zeilen, die der ersten hinzugefügt wurden Indexseite, wenn der Index leer war zu Beginn der Operation.
  • Zeilen zu bestehenden hinzugefügt Indexseiten.
  • Zeilen verschoben zwischen den Seiten durch eine Seitenteilung.

Andernfalls werden Zeilen aus dem bestellten Insert-Stream zu einer brandneuen Seite hinzugefügt mit einem optimierten und minimal protokollierten Codepfad. Sobald so viele Zeilen wie möglich auf die neue Seite geschrieben wurden, wird sie direkt in die bestehende Zielindexstruktur verlinkt.

Die neu hinzugefügte Seite wird nicht unbedingt voll sein (obwohl das offensichtlich der Idealfall ist), da SQL Server darauf achten muss, der neuen Seite keine Zeilen hinzuzufügen, die logisch zu einer vorhandenen gehören Indexseite. Die neue Seite wird als Einheit in den Index „eingefügt“, sodass wir keine Zeilen auf der neuen Seite haben können, die woanders hingehören. Dies ist hauptsächlich ein Problem, wenn Zeilen innerhalb hinzugefügt werden dem vorhandenen Schlüsselbereich des Index, und nicht vor dem Start oder nach dem Ende des vorhandenen Indexschlüsselbereichs.

Es ist immer noch möglich um neue Seiten innerhalb hinzuzufügen der vorhandene Indexschlüsselbereich, aber die neuen Zeilen müssen höher sortiert werden als der höchste Schlüssel auf dem vorhergehenden vorhandene Indexseite und sortieren Sie niedriger als den niedrigsten Schlüssel auf dem folgenden vorhandene Indexseite. Für die beste Chance, eine minimale Protokollierung zu erreichen Stellen Sie unter diesen Umständen sicher, dass sich die eingefügten Zeilen so weit wie möglich nicht mit vorhandenen Zeilen überschneiden.

DMLRequestSort-Bedingungen

Denken Sie an FastLoadContext kann nur aktiviert werden, wenn DMLRequestSort auf true gesetzt ist für die entsprechende Indexeinlage Operator im Ausführungsplan.

Es gibt zwei Hauptcodepfade, die DMLRequestSort festlegen können auf wahr für Indexeinsätze. Beide Wege gibt wahr zurück ist ausreichend.

1. FOptimizeInsert

Das sqllang!CUpdUtil::FOptimizeInsert Code erfordert:

  • Mehr als 250 Zeilen geschätzt eingefügt werden; und
  • Mehr als 2 Seiten geschätzt Datengröße einfügen; und
  • Der Zielindex muss weniger als 3 Blattseiten haben .

Diese Bedingungen sind dieselben wie bei RowsetBulk auf einem leeren gruppierten Index, mit einer zusätzlichen Anforderung von nicht mehr als zwei Seiten auf Indexblattebene. Beachten Sie sorgfältig, dass sich dies auf die Größe des vorhandenen Indexes bezieht vor der Einfügung, nicht die geschätzte Größe der hinzuzufügenden Daten.

Das folgende Skript ist eine Modifikation der Demo, die in früheren Teilen dieser Serie verwendet wurde. Es zeigt minimale Protokollierung wenn vorher weniger als drei Indexseiten gefüllt sind der Test INSERT...SELECT läuft. Das Testtabellenschema ist so ausgelegt, dass 130 Zeilen auf eine einzelne 8-KB-Seite passen, wenn die Zeilenversionierung für die Datenbank deaktiviert ist. Der Multiplikator im ersten TOP -Klausel kann geändert werden, um die Anzahl der vorhandenen Indexseiten vorher zu bestimmen der Test INSERT...SELECT wird ausgeführt:

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

Wenn der gruppierte Index mit 3 Seiten vorab geladen wird , wird die Testeinlage vollständig protokolliert (Detailaufzeichnungen des Transaktionsprotokolls wurden der Kürze halber weggelassen):

Wenn die Tabelle vorab mit nur 1 oder 2 Seiten geladen ist , der Testeinsatz ist minimal protokolliert :

Wenn die Tabelle nicht vorab geladen ist Mit beliebigen Seiten entspricht der Test dem Ausführen der leeren gruppierten Tabellendemo aus Teil zwei, jedoch ohne den TABLOCK Hinweis:

Die ersten 130 Zeilen werden vollständig protokolliert . Das liegt daran, dass der Index leer war, bevor wir angefangen haben, und 130 Zeilen auf die erste Seite passen. Denken Sie daran, dass die erste Seite immer vollständig protokolliert wird, wenn FastLoadContext verwendet wird und der Index vorher leer war. Die restlichen 139 Zeilen werden mit minimaler Protokollierung eingefügt .

Wenn ein TABLOCK Der Einfügung wird ein Hinweis hinzugefügt, alle Seiten werden minimal protokolliert (einschließlich des ersten), da das Laden des leeren Clustered-Index nun für den RowsetBulk qualifiziert Mechanismus (auf Kosten eines Sch-M sperren).

2. FDemandRowsSortedForPerformance

Wenn der FOptimizeInsert Tests schlagen fehl, DMLRequestSort kann immer noch auf true gesetzt werden durch eine zweite Reihe von Tests in sqllang!CUpdUtil::FDemandRowsSortedForPerformance Code. Diese Bedingungen sind etwas komplexer, daher ist es sinnvoll, einige Parameter zu definieren:

  • P – Anzahl der vorhandenen Seiten auf Blattebene im Zielindex .
  • Igeschätzt Anzahl der einzufügenden Zeilen.
  • R =P / I (Zielseiten pro eingefügter Zeile).
  • T – Anzahl der Zielpartitionen (1 für unpartitioniert).

Die Logik zur Bestimmung des Werts von DMLRequestSort ist dann:

  • Wenn P <= 16 false zurückgeben , sonst :
    • Wenn R < 8 :
      • Wenn P > 524 true zurückgeben , andernfalls false .
    • Wenn R >= 8 :
      • Wenn T > 1 und I > 250 true zurückgeben , andernfalls false .

Die obigen Tests werden vom Abfrageprozessor während der Plankompilierung ausgewertet. Es gibt eine Endbedingung vom Speicher-Engine-Code ausgewertet (IndexDataSetSession::WakeUpInternal ) zur Ausführungszeit:

  • DMLRequestSort ist derzeit wahr; und
  • I >= 100 .

Als nächstes werden wir all diese Logik in überschaubare Teile zerlegen.

Mehr als 16 vorhandene Zielseiten

Der erste Test P <= 16 bedeutet, dass Indizes mit weniger als 17 vorhandenen Blattseiten nicht für FastLoadContext qualifiziert sind über diesen Codepfad. Um das ganz klar zu sagen, P ist die Anzahl der Seiten auf Blattebene im Zielindex vorher das INSERT...SELECT wird ausgeführt.

Um diesen Teil der Logik zu demonstrieren, werden wir die geclusterte Testtabelle mit 16 Seiten vorab laden von Dateien. Dies hat zwei wichtige Auswirkungen (denken Sie daran, dass beide Codepfade false zurückgeben müssen um mit einem false zu enden Wert für DMLRequestSort ):

  1. Es stellt sicher, dass das vorherige FOptimizeInsert Test schlägt fehl , weil die dritte Bedingung nicht erfüllt ist (P < 3 ).
  2. Der P <= 16 Bedingung in FDemandRowsSortedForPerformance auch nicht erfüllt werden.

Wir erwarten daher FastLoadContext nicht freigeschaltet werden. Das modifizierte Demoskript lautet:

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)';

Alle 269 Zeilen werden vollständig protokolliert wie vorhergesagt:

Beachten Sie, dass das obige Skript nie wird, egal wie hoch wir die Anzahl der einzufügenden neuen Zeilen festlegen erzeugen minimale Protokollierung wegen P <= 16 test (und P < 3 in FOptimizeInsert testen ).

Wenn Sie die Demo selbst mit einer größeren Anzahl von Zeilen ausführen möchten, kommentieren Sie den Abschnitt aus, in dem einzelne Transaktionsprotokolleinträge angezeigt werden. Andernfalls warten Sie sehr lange und SSMS kann abstürzen. (Um fair zu sein, könnte es das trotzdem tun, aber warum das Risiko erhöhen.)

Verhältnis Seiten pro eingefügter Zeile

Wenn es 17 oder mehr sind Blattseiten im bestehenden Index, die bisherigen P <= 16 Test wird nicht fehlschlagen. Der nächste Abschnitt der Logik befasst sich mit dem Verhältnis von vorhandenen Seiten zu neu eingefügten Zeilen . Dies muss auch bestanden werden, um eine minimale Protokollierung zu erreichen . Zur Erinnerung, die relevanten Bedingungen sind:

  • Verhältnis R =P / I .
  • Wenn R < 8 :
    • Wenn P > 524 true zurückgeben , andernfalls false .

Wir müssen uns auch an den abschließenden Speicher-Engine-Test für mindestens 100 Zeilen erinnern:

  • I >= 100 .

Diese Bedingungen ein bisschen neu organisieren, alle Folgendes muss zutreffen:

  1. P > 524 (vorhandene Indexseiten)
  2. I >= 100 (geschätzte eingefügte Zeilen)
  3. P / I < 8 (Verhältnis R )

Es gibt mehrere Möglichkeiten, diese drei Bedingungen gleichzeitig zu erfüllen. Wählen wir die minimal möglichen Werte für P (525) und I (100) gibt ein R Wert von (525/100) =5,25. Dies erfüllt die (R < 8 test), also erwarten wir, dass diese Kombination zu minimaler Protokollierung führt :

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)';

Das 100-Zeilen-INSERT...SELECT tatsächlich minimal protokolliert :

Reduzierung der geschätzten Eingefügte Zeilen bis 99 (Breaking I >= 100 ) und/oder die Anzahl der vorhandenen Indexseiten auf 524 reduzieren (brechen von P > 524 ) führt zu einer vollständigen Protokollierung . Wir könnten auch Änderungen wie R vornehmen ist nicht länger kleiner als 8, um vollständige Protokollierung zu erzeugen . Setzen Sie beispielsweise P = 1000 und I = 125 ergibt R = 8 , mit den folgenden Ergebnissen:

Die 125 eingefügten Zeilen wurden vollständig protokolliert wie erwartet. (Dies liegt nicht an der vollen Protokollierung der ersten Seite, da der Index vorher nicht leer war.)

Seitenverhältnis für partitionierte Indizes

Wenn alle vorhergehenden Tests fehlschlagen, erfordert der verbleibende Test R >= 8 und kann nur zufrieden sein, wenn die Anzahl der Partitionen (T ) ist größer als 1 und geschätzt sind es mehr als 250 eingefügte Zeilen (I ). Rückruf:

  • Wenn R >= 8 :
    • Wenn T > 1 und I > 250 true zurückgeben , andernfalls false .

Eine Feinheit:Für partitioniert Indizes gilt die Regel, dass alle Zeilen der ersten Seite vollständig protokolliert werden (für einen anfänglich leeren Index), pro Partition . Für ein Objekt mit 15.000 Partitionen bedeutet das 15.000 vollständig protokollierte „erste“ Seiten.

Zusammenfassung und abschließende Gedanken

Die im Hauptteil beschriebenen Formeln und die Reihenfolge der Auswertung basieren auf der Codeinspektion mit einem Debugger. Sie wurden in einer Form präsentiert, die das Timing und die Reihenfolge, die im echten Code verwendet werden, genau wiedergibt.

Es ist möglich, diese Bedingungen ein wenig neu zu ordnen und zu vereinfachen, um eine präzisere Zusammenfassung der praktischen Anforderungen für minimale Protokollierung zu erhalten beim Einfügen in einen B-Baum mit INSERT...SELECT . Die folgenden verfeinerten Ausdrücke verwenden die folgenden drei Parameter:

  • P =Anzahl der vorhandenen Seiten auf Blattebene indexieren.
  • I =geschätzt Anzahl der einzufügenden Zeilen.
  • S =geschätzt Datengröße in 8-KB-Seiten einfügen.

Rowset-Massenladen

  • Verwendet sqlmin!RowsetBulk .
  • Erfordert ein leeres gruppiertes Indexziel mit TABLOCK (oder gleichwertig).
  • Benötigt DMLRequestSort = true auf der Clustered Index Insert Betreiber.
  • DMLRequestSort auf true gesetzt ist wenn I > 250 und S > 2 .
  • Alle eingefügten Zeilen werden minimal protokolliert .
  • Ein Sch-M Sperre verhindert gleichzeitigen Zugriff auf Tabellen.

Kontext für schnelles Laden

  • Verwendet sqlmin!FastLoadContext .
  • Aktiviert minimal protokolliert fügt in B-Tree-Indizes ein:
    • Geclustert oder nicht geclustert.
    • Mit oder ohne Tabellensperre.
    • Zielindex leer oder nicht.
  • Benötigt DMLRequestSort = true auf dem zugehörigen Index Insert Planbetreiber.
  • Nur Zeilen, die auf ganz neue Seiten geschrieben werden werden in großen Mengen geladen und minimal protokolliert .
  • Die erste Seite eines zuvor leeren Index Partition wird immer vollständig protokolliert .
  • Absolutes Minimum von I >= 100 .
  • Erfordert Trace-Flag 610 vor SQL Server 2016.
  • Standardmäßig ab SQL Server 2016 verfügbar (Trace-Flag 692 deaktiviert).

DMLRequestSort auf true gesetzt ist für:

  • Jeder Index (partitioniert oder nicht) if:
    • I > 250 und P < 3 und S > 2; oder
    • I >= 100 und P > 524 und P < I * 8

Nur für partitionierte Indizes (bei> 1 Partition), DMLRequestSort wird ebenfalls auf true gesetzt wenn:

  • I > 250 und P > 16 und P >= I * 8

Es gibt ein paar interessante Fälle, die sich aus diesen FastLoadContext ergeben Bedingungen:

  • Alle Einfügungen in eine nicht partitionierte Index mit zwischen 3 und 524 (inklusive) vorhandene Blattseiten werden vollständig protokolliert unabhängig von der Anzahl und Gesamtgröße der hinzugefügten Zeilen. Dies wirkt sich am deutlichsten auf große Einfügungen in kleine (aber nicht leere) Tabellen aus.
  • Alle Einfügungen in eine partitionierte Index mit zwischen 3 und 16 vorhandene Seiten werden vollständig protokolliert .
  • Große Einsätze zu groß nicht partitioniert Indizes dürfen nicht minimal protokolliert werden aufgrund der Ungleichung P < I * 8 . Wenn P groß ist, wird ein entsprechend großer geschätzt Anzahl der eingefügten Zeilen (I ) erforderlich. Beispielsweise kann ein Index mit 8 Millionen Seiten die minimale Protokollierung nicht unterstützen beim Einfügen von 1 Million Zeilen oder weniger.

Nicht gruppierte Indizes

Die gleichen Überlegungen und Berechnungen, die in den Demos für geclusterte Indizes angewendet wurden, gelten für nonclustered B-Tree-Indizes ebenfalls, solange der Index von einem dedizierten Planbetreiber verwaltet wird (eine breite , oder pro Index planen). Nonclustered-Indizes, die von einem Basistabellenoperator verwaltet werden (z. B. Clustered Index Insert ) sind nicht für FastLoadContext geeignet .

Beachten Sie, dass die Formelparameter für jedes nonclustered neu ausgewertet werden müssen Indexoperator – berechnete Zeilengröße, Anzahl vorhandener Indexseiten und Kardinalitätsschätzung.

Allgemeine Bemerkungen

Achten Sie auf niedrige Kardinalitätsschätzungen in der Indexeinlage Operator, da diese das I beeinflussen und S Parameter. Wenn ein Schwellenwert aufgrund eines Kardinalitätsschätzungsfehlers nicht erreicht wird, wird die Einfügung vollständig protokolliert .

Denken Sie daran, dass DMLRequestSort wird mit dem Plan zwischengespeichert — es wird nicht bei jeder Ausführung eines wiederverwendeten Plans ausgewertet. Dies kann eine Form des bekannten Parameter-Sensitivity-Problems (auch bekannt als „Parameter-Sniffing“) einführen.

Der Wert von P (Indexblattseiten) wird nicht aktualisiert am Anfang jeder Aussage. Die aktuelle Implementierung speichert den Wert für den gesamten Batch . Dies kann unerwartete Nebenwirkungen haben. Zum Beispiel eine TRUNCATE TABLE in der gleichen Charge als INSERT...SELECT setzt P nicht zurück für die in diesem Artikel beschriebenen Berechnungen auf null setzen – sie werden weiterhin den vorab abgeschnittenen Wert verwenden, und eine Neukompilierung wird nicht helfen. Eine Problemumgehung besteht darin, große Änderungen in separaten Stapeln einzureichen.

Trace-Flags

Es ist möglich, FDemandRowsSortedForPerformance zu erzwingen um true zurückzugeben durch die Einstellung undokumentiert und nicht unterstützt Trace-Flag 2332, wie ich in Optimieren von T-SQL-Abfragen geschrieben habe, die Daten ändern. Wenn TF 2332 aktiv ist, die geschätzte Anzahl einzufügender Zeilen muss trotzdem mindestens 100 sein . TF 2332 beeinflusst die minimale Protokollierung Entscheidung für FastLoadContext nur (es gilt für partitionierte Heaps bis zu DMLRequestSort betrifft, hat aber keine Auswirkung auf den Heap selbst, da FastLoadContext gilt nur für Indizes).

Ein wide/per-index plan shape for nonclustered index maintenance kann für Rowstore-Tabellen mit Ablaufverfolgungs-Flag 8790 erzwungen werden (nicht offiziell dokumentiert, aber in einem Knowledge Base-Artikel sowie in meinem Artikel erwähnt, der für TF2332 oben verlinkt ist).

Alles von Sunil Agarwal vom SQL Server-Team:

  • Was sind die Massenimportoptimierungen?
  • Bulk-Import-Optimierungen (minimale Protokollierung)
  • Minimale Protokollierungsänderungen in SQL Server 2008
  • Minimale Protokollierungsänderungen in SQL Server 2008 (Teil 2)
  • Minimale Protokollierungsänderungen in SQL Server 2008 (Teil 3)