Ich habe eine SQL-Anweisung, die Zeilen in eine Tabelle mit einem Clustered-Index für die 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 Clustered-Index-Spalte zu verwenden, oder würde ein erzielter Gewinn durch die für die ORDER BY-Klausel erforderliche zusätzliche Sortierung negiert?
Wie aus den anderen Antworten bereits hervorgeht, kann SQL Server explizit sicherstellen, dass die Zeilen vor dem insert
in gruppierter Indexreihenfolge sortiert werden.
Dies hängt davon ab, ob für den Clustered-Index-Operator im Plan die Eigenschaft DMLRequestSort
festgelegt ist (was wiederum von der geschätzten Anzahl der eingefügten Zeilen abhängt).
Wenn Sie feststellen, dass SQL Server dies aus irgendeinem Grund unterschätzt, können Sie der Abfrage SELECT
ein explizites ORDER BY
Hinzufügen, um die Seitenteilung zu minimieren und die Fragmentierung der Operation INSERT
zu minimieren
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 an, dass T
massiv 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
Bei T2
Ist die Fragmentierung jedoch 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 manchmal SQL Server 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 newsequentialid
Clustered-Indexschlüssel. In Versionen von SQL Server vor Denali fügt SQL Server eine unnötige und möglicherweise teure Sortieroperation hinz . 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 von Bar
, die unter dem Schwellenwert liegt, bei dem eine Sortierung zum Plan hinzugefügt wird. Wie in den Kommentaren unten ausgeführt, bedeutet dies jedoch, dass die Einfügung die minimale Protokollierung leider nicht nutzen kann.
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 der Optimierer erkennen, dass die Daten bereits sortiert sind, und dies erneut unterlassen. Beachten Sie, dass der ausgewählte Ausführungsplan von Lauf zu Lauf 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, fügen Sie sie Ihrer Frage zur Kommentierung hinzu.
Bearbeiten: 2011-10-28 17:00
@ Gonsalus Antwort scheint zu zeigen, dass immer eine Sortieroperation stattfindet, dies ist nicht der Fall. Demoskripte erforderlich!
Da die Skripte ziemlich groß wurden, habe ich sie nach Gist verschoben. Zur Erleichterung des Experimentierens verwenden die Skripte den SQLCMD-Modus. Die Tests laufen auf 2K5SP3, Dual Core, 8 GB.
Die Insert-Tests decken drei Szenarien ab:
Führen Sie zuerst 25 Zeilen ein.
Alle drei Ausführungspläne sind gleich, es erfolgt nirgendwo im Plan eine Sortierung, und der Clustered-Index-Scan lautet "geordnet = falsch".
Zweiter Lauf, 26 Zeilen einfügen.
Diesmal unterscheiden sich die Pläne.
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 benötigt 25 keine Sortierung, 26 (2K5SP3, Dual Core, 8 GB)
Das SQLCMD-Skript enthält Variablen, mit denen sich die Größe der Zeilen in der Tabelle (Ändern der Seitendichte) und die Anzahl der Zeilen in dbo.MyTable vor den zusätzlichen Einfügungen ändern können. Nach meinen Tests hat keiner einen Einfluss auf den Wendepunkt.
Wenn Leser dazu neigen, bitte führen Sie die Skripte aus und fügen Sie Ihren Wendepunkt als Kommentar hinzu. Interessiert zu hören, ob es zwischen Prüfständen und/oder Versionen variiert.
Bearbeiten: 2011-10-28 20:15
Wiederholte Tests auf demselben Rig, jedoch mit 2K8R2. Diesmal beträgt der Wendepunkt 251 Zeilen. Auch hier hat das Variieren der Seitendichte und der vorhandenen Zeilenanzahl keine Auswirkung.
Die Klausel ORDER BY
in der Anweisung SELECT
ist redundant.
Es ist redundant, da die Zeilen, die eingefügt werden sollen, wenn sie sortiert werden müssen trotzdem sortiert sind.
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;
Aktivieren Sie die Textanzeige der tatsächlichen Abfragepläne, damit wir sehen können, welche Aufgaben vom Abfrageprozessor ausgeführt werden.
SET STATISTICS PROFILE ON;
GO
Lassen Sie uns nun INSERT
2K-Zeilen ohne ORDER BY
-Klausel 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 können, gibt es einen Sortieroperator, bevor das eigentliche INSERT erfolgt.
Lassen Sie uns nun die Tabelle löschen und INSERT
2k Zeilen mit der Klausel ORDER BY
in die Tabelle einfügen.
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 es sich um denselben Ausführungsplan handelt, der für die Anweisung INSERT
ohne die Klausel ORDER BY
verwendet wurde.
Jetzt ist die Operation Sort
nicht immer erforderlich, wie Mark Smith in einer anderen Antwort gezeigt hat (wenn die Anzahl der einzufügenden Zeilen gering ist), sondern der Code ORDER BY
-Klausel ist in diesem Fall immer noch redundant, da selbst mit einer expliziten ORDER BY
keine Sort
-Operation vom Abfrageprozessor generiert wird.
Sie können eine INSERT
-Anweisung in eine Tabelle mit einem Clustered-Index optimieren, indem Sie ein minimal protokolliertes INSERT
verwenden. Dies ist jedoch für diese Frage nicht zulässig.
Aktualisiert am 02.11.2011: Wie Mark Smith gezeigt hat , INSERT
s in eine Tabelle mit einem Cluster Der Index muss möglicherweise nicht immer sortiert werden. In diesem Fall ist die Klausel ORDER BY
jedoch auch redundant.