Effizientes Einfügen in eine Tabelle mit gruppiertem Index

28

Ich habe eine SQL-Anweisung, die Zeilen in eine Tabelle mit einem Clustered-Index in der Spalte TRACKING_NUMBER einfügt.

Z.B:

INSERT INTO TABL_NAME (TRACKING_NUMBER, COLB, COLC) 
SELECT TRACKING_NUMBER, COL_B, COL_C 
FROM STAGING_TABLE

Meine Frage ist: Hilft es, eine ORDER BY-Klausel in der SELECT-Anweisung für die Spalte mit dem gruppierten Index zu verwenden, oder wird ein erzielter Gewinn durch die zusätzliche Sortierung negiert, die für die ORDER BY-Klausel erforderlich ist?

GWR
quelle

Antworten:

18

Wie in den anderen Antworten bereits angegeben, kann SQL Server explizit sicherstellen, dass die Zeilen vor dem in Gruppen zusammengefassten Index in der angegebenen Reihenfolge sortiert werden insert.

Dies hängt davon ab, ob für den gruppierten Indexoperator im Plan die DMLRequestSortEigenschaft festgelegt wurde (was wiederum von der geschätzten Anzahl der eingefügten Zeilen abhängt).

Wenn Sie feststellen , dass SQL Server dies wird unterschätzt welchen Grund auch immer Sie das Hinzufügen einer expliziten Nutzen ziehen kann , ORDER BYum die SELECTAbfrage - Seite Splits zu minimieren und die daraus resultierende Fragmentierung der INSERTOperation

Beispiel:

use tempdb;

GO

CREATE TABLE T(N INT PRIMARY KEY,Filler char(2000))

CREATE TABLE T2(N INT PRIMARY KEY,Filler char(2000))

GO

DECLARE @T TABLE (U UNIQUEIDENTIFIER PRIMARY KEY DEFAULT NEWID(),N int)

INSERT INTO @T(N)
SELECT number 
FROM master..spt_values
WHERE type = 'P' AND number BETWEEN 0 AND 499

/*Estimated row count wrong as inserting from table variable*/
INSERT INTO T(N)
SELECT T1.N*1000 + T2.N
FROM @T T1, @T T2

/*Same operation using explicit sort*/    
INSERT INTO T2(N)
SELECT T1.N*1000 + T2.N
FROM @T T1, @T T2
ORDER BY T1.N*1000 + T2.N


SELECT avg_fragmentation_in_percent,
       fragment_count,
       page_count,
       avg_page_space_used_in_percent,
       record_count
FROM   sys.dm_db_index_physical_stats(2, OBJECT_ID('T'), NULL, NULL, 'DETAILED')
;  


SELECT avg_fragmentation_in_percent,
       fragment_count,
       page_count,
       avg_page_space_used_in_percent,
       record_count
FROM   sys.dm_db_index_physical_stats(2, OBJECT_ID('T2'), NULL, NULL, 'DETAILED')
;  

Zeigt, dass Tmassiv fragmentiert ist

avg_fragmentation_in_percent fragment_count       page_count           avg_page_space_used_in_percent record_count
---------------------------- -------------------- -------------------- ------------------------------ --------------------
99.3116118225536             92535                92535                67.1668272794663               250000
99.5                         200                  200                  74.2868173956017               92535
0                            1                    1                    32.0978502594514               200

Aber für die T2Fragmentierung ist minimal

avg_fragmentation_in_percent fragment_count       page_count           avg_page_space_used_in_percent record_count
---------------------------- -------------------- -------------------- ------------------------------ --------------------
0.376                        262                  62500                99.456387447492                250000
2.1551724137931              232                  232                  43.2438349394613               62500
0                            1                    1                    37.2374598468001               232

Umgekehrt möchten Sie SQL Server manchmal dazu zwingen, die Zeilenanzahl zu unterschätzen, wenn Sie wissen, dass die Daten bereits vorsortiert sind und eine unnötige Sortierung vermeiden möchten. Ein bemerkenswertes Beispiel ist das Einfügen einer großen Anzahl von Zeilen in eine Tabelle mit einem newsequentialidClustered-Indexschlüssel. In Versionen von SQL Server vor Denali fügt SQL Server einen unnötigen und möglicherweise teuren Sortiervorgang hinzu . Dies kann vermieden werden durch

DECLARE @var INT =2147483647

INSERT INTO Foo
SELECT TOP (@var) *
FROM Bar

SQL Server schätzt dann, dass 100 Zeilen eingefügt werden, unabhängig von der Größe, Bardie unter dem Schwellenwert liegt, ab dem dem Plan eine Sortierung hinzugefügt wird. Wie in den Kommentaren unten ausgeführt, bedeutet dies jedoch, dass die Beilage die minimale Protokollierung leider nicht nutzen kann.

Martin Smith
quelle
12

Wenn der Optimierer entscheidet, dass es effizienter ist, die Daten vor dem Einfügen zu sortieren, geschieht dies irgendwo vor dem Einfügeoperator. Wenn Sie eine Sortierung als Teil Ihrer Abfrage einführen, sollte das Optimierungsprogramm erkennen, dass die Daten bereits sortiert sind, und dies erneut auslassen. Beachten Sie, dass der gewählte Ausführungsplan von Ausführung zu Ausführung variieren kann, abhängig von der Anzahl der Zeilen, die aus Ihrer Staging-Tabelle eingefügt wurden.

Wenn Sie Ausführungspläne des Prozesses mit und ohne explizite Sortierung erfassen können, hängen Sie sie zur Kommentierung an Ihre Frage an.

Bearbeiten: 2011-10-28 17:00

@ Gonsalus Antwort scheint zu zeigen, dass immer eine Sortieroperation stattfindet, dies ist jedoch nicht der Fall. Demo-Skripte erforderlich!

Da die Skripte ziemlich umfangreich wurden, habe ich sie nach Gist verschoben . Um das Experimentieren zu vereinfachen, verwenden die Skripte den SQLCMD-Modus. Tests laufen auf 2K5SP3, Dual Core, 8GB.

Die Einfügetests decken drei Szenarien ab:

  1. Staging-Daten-Clustered-Index in derselben Reihenfolge wie das Ziel.
  2. Staging-Daten-Clustered-Index in umgekehrter Reihenfolge.
  3. Durch col2 gruppierte Staging-Daten, die eine zufällige INT enthalten.

Erster Lauf, Einfügen von 25 Zeilen.

1. Lauf, 25 Reihen

Alle drei Ausführungspläne sind gleich, es wird nirgendwo im Plan eine Sortierung vorgenommen, und der Clustered-Index-Scan lautet "ordered = false".

Zweiter Durchgang, Einfügen von 26 Reihen.

2. Lauf, 26 Reihen

Diesmal sind die Pläne unterschiedlich.

  • Die erste zeigt den Clustered-Index-Scan wie bestellt = falsch. Es ist keine Sortierung aufgetreten, da die Quelldaten entsprechend sortiert sind.
  • Im zweiten Fall wird der Clustered-Index-Scan wie bestellt = true, rückwärts ausgeführt. Wir haben also keine Sortieroperation, aber die Notwendigkeit, die Daten zu sortieren, wird vom Optimierer erkannt und in umgekehrter Reihenfolge gescannt.
  • Der dritte zeigt einen Sortieroperator.

Es gibt also einen Wendepunkt, an dem der Optimierer eine Sortierung für notwendig hält. Wie @MartinSmith zeigt, scheint dies auf den geschätzten einzufügenden Zeilen zu beruhen. Auf meinem Prüfstand ist für 25 keine Sortierung erforderlich, für 26 (2K5SP3, Dual Core, 8 GB).

Das Skript SQLCMD enthält Variablen, mit denen die Größe der Zeilen in der Tabelle geändert werden kann (Änderung der Seitendichte) und die Anzahl der Zeilen in dbo.MyTable vor den zusätzlichen Einfügungen. Beides hat nach meinem Test keinen Einfluss auf den Wendepunkt.

Wenn Leser dazu neigen, führen Sie bitte die Skripte aus und fügen Sie Ihren Tipp als Kommentar hinzu. Interessiert zu hören, ob es sich um unterschiedliche Prüfstände und / oder Versionen handelt.

Edit: 2011-10-28 20:15

Wiederholte Tests auf dem gleichen Rig, aber mit 2K8R2. Diesmal beträgt der Wendepunkt 251 Zeilen. Auch hier hat das Ändern der Seitendichte und der Anzahl der vorhandenen Zeilen keine Auswirkung.

Mark Storey-Smith
quelle
8

Die ORDER BYKlausel in der SELECTAnweisung ist überflüssig.

Es ist redundant, da die einzufügenden Zeilen, falls sie sortiert werden müssen , ohnehin sortiert werden.

Lassen Sie uns einen Testfall erstellen.

CREATE TABLE #Test (
    id INTEGER NOT NULL
);

CREATE UNIQUE CLUSTERED INDEX CL_Test_ID ON #Test (id);

CREATE TABLE #Sequence (
    number INTEGER NOT NULL
);

INSERT INTO #Sequence
SELECT number FROM master..spt_values WHERE name IS NULL;

Lassen Sie uns die Textanzeige der tatsächlichen Abfragepläne aktivieren, damit wir sehen können, welche Aufgaben vom Abfrageprozessor ausgeführt werden.

SET STATISTICS PROFILE ON;
GO

Lassen Sie uns nun INSERT2K-Zeilen ohne ORDER BYKlausel in die Tabelle einfügen.

INSERT INTO #Test
SELECT number
  FROM #Sequence

Der tatsächliche Ausführungsplan für diese Abfrage lautet wie folgt.

INSERT INTO #Test  SELECT number    FROM #Sequence
  |--Clustered Index Insert(OBJECT:([tempdb].[dbo].[#Test]), SET:([tempdb].[dbo].[#Test].[id] = [tempdb].[dbo].[#Sequence].[number]))
       |--Top(ROWCOUNT est 0)
            |--Sort(ORDER BY:([tempdb].[dbo].[#Sequence].[number] ASC))
                 |--Table Scan(OBJECT:([tempdb].[dbo].[#Sequence]))

Wie Sie sehen, gibt es einen Sortieroperator, bevor das eigentliche INSERT auftritt.

Löschen wir nun die Tabelle und INSERTfügen 2k Zeilen mit der ORDER BYKlausel in die Tabelle ein .

TRUNCATE TABLE #Test;
GO

INSERT INTO #Test
SELECT number
  FROM #Sequence
 ORDER BY number

Der tatsächliche Ausführungsplan für diese Abfrage lautet wie folgt.

INSERT INTO #Test  SELECT number    FROM #Sequence   ORDER BY number
  |--Clustered Index Insert(OBJECT:([tempdb].[dbo].[#Test]), SET:([tempdb].[dbo].[#Test].[id] = [tempdb].[dbo].[#Sequence].[number]))
       |--Top(ROWCOUNT est 0)
            |--Sort(ORDER BY:([tempdb].[dbo].[#Sequence].[number] ASC))
                 |--Table Scan(OBJECT:([tempdb].[dbo].[#Sequence]))

Beachten Sie, dass dies derselbe Ausführungsplan ist, der für die INSERTAnweisung ohne die ORDER BYKlausel verwendet wurde.

Nun ist die SortOperation nicht immer erforderlich, wie Mark Smith in einer anderen Antwort gezeigt hat (wenn die Anzahl der einzufügenden Zeilen niedrig ist), aber die ORDER BYKlausel ist in diesem Fall immer noch redundant, da selbst mit einer expliziten Operation ORDER BYkeine SortOperation generiert wird durch den Abfrageprozessor.

Sie können eine INSERTAnweisung in eine Tabelle mit einem Clustered-Index optimieren , indem Sie eine minimal protokollierte Anweisung verwenden. INSERTDies ist jedoch für diese Frage nicht relevant.

Aktualisiert am 2011-11-02: Wie Mark Smith gezeigt hat, muss INSERTeine Tabelle mit einem Clustered-Index möglicherweise nicht immer sortiert werden - die ORDER BYKlausel ist jedoch auch in diesem Fall redundant.

gonsalu
quelle