Mám příkaz SQL, který vkládá řádky do tabulky se seskupeným indexem ve sloupci TRACKING_NUMBER.
NAPŘ.:
INSERT INTO TABL_NAME (TRACKING_NUMBER, COLB, COLC)
SELECT TRACKING_NUMBER, COL_B, COL_C
FROM STAGING_TABLE
Moje otázka zní - pomůže to použít klauzuli ORDER BY v příkazu SELECT pro sloupec seskupeného indexu, nebo by byl jakýkoli dosažený zisk negován zvláštním druhem požadovaným pro klauzuli ORDER BY?
Protože ostatní odpovědi již naznačují, SQL Server může nebo nemusí explicitně zajistit, že řádky jsou seřazeny v pořadí seskupených indexů před insert
.
To závisí na tom, zda má klastrovaný indexový operátor v plánu sadu vlastností DMLRequestSort
(což zase závisí na odhadovaném počtu řádků, které jsou vloženy).
Pokud zjistíte, že SQL Server to z jakéhokoli důvodu podceňuje, můžete mít prospěch z přidání explicitního ORDER BY
k dotazu SELECT
k minimalizaci rozdělení stránek a následné fragmentaci z operace INSERT
Příklad:
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')
;
Ukazuje, že T
je masivně roztříštěný
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
Ale pro T2
fragmentace je minimální
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
Naopak někdy budete chtít donutit SQL Server, aby podceňoval počet řádků, když víte, že data jsou již předem roztříděna a chcete zabránit zbytečnému třídění. Jedním z pozoruhodných příkladů je vložení velkého počtu řádků do tabulky pomocí seskupeného indexového klíče newsequentialid
. Ve verzích SQL Serveru před Denali SQL Server přidává zbytečnou a potenciálně nákladnou operaci řazení . Tomu lze zabránit
DECLARE @var INT =2147483647
INSERT INTO Foo
SELECT TOP (@var) *
FROM Bar
SQL Server poté odhadne, že bude vloženo 100 řádků bez ohledu na velikost Bar
, která je pod prahem, při kterém je do plánu přidáno řazení. Jak je však uvedeno v poznámkách níže, znamená to, že vložka bohužel nebude moci využít výhody minimálního protokolování.
Optimalizátor rozhodne, že by bylo efektivnější třídit data před vložením, učiní tak někde proti proudu od operátora vložení. Pokud v rámci dotazu uvedete řazení, měl by si optimalizátor uvědomit, že data jsou již roztříděna, a vynechat to znovu. Všimněte si, že vybraný prováděcí plán se může v jednotlivých bězích lišit v závislosti na počtu řádků vložených z pracovní tabulky.
Pokud dokážete zachytit prováděcí plány procesu s explicitním uspořádáním a bez něj, připojte je ke své otázce k vyjádření.
Edit: 2011-10-28 17:00
@ Gonsaluova odpověď ukazuje, že operace řazení vždy nastane, není tomu tak. Demo skripty povinné!
Protože se skripty dostaly do velké míry, přesunul jsem je do Gist . Pro snadnější experimentování používají skripty režim SQLCMD. Testy probíhají na 2K5SP3, dvoujádrový, 8 GB.
Testy vložení pokrývají tři scénáře:
První spuštění, vložení 25 řádků.
Všechny tři plány provádění jsou stejné, nikde v plánu nedochází k žádnému řazení a skenování seskupeného indexu je "uspořádáno = nepravdivé".
Druhý běh, vložení 26 řádků.
Tentokrát se plány liší.
Existuje tedy bod zlomu, kde optimalizátor pokládá za nutné. Jak ukazuje @MartinSmith, zdá se, že to vychází z odhadovaných řádků, které mají být vloženy. Na mé testovací soupravě 25 není potřeba takové řazení, 26 ano (2K5SP3, duální jádro, 8 GB)
Skript SQLCMD obsahuje proměnné, které umožňují změnit velikost řádků v tabulce (změnit hustotu stránky) a počet řádků v dbo.MyTable před dalšími vloženími. Z mého testování nemá žádný vliv na bod zlomu.
Pokud jsou některí čtenáři tak nakloněni, prosím spusťte skripty a přidejte svůj tipovací bod jako komentář. Zájem o slyšení, zda se liší v různých testovacích zařízeních a/nebo verzích.
Edit: 2011-10-28 20:15
Opakované zkoušky na stejné soupravě, ale s 2K8R2. Tentokrát je bodem překlopení 251 řádků. Změna hustoty stránky a počtu existujících řádků opět nemá žádný účinek.
Klauzule ORDER BY
V příkazu SELECT
je nadbytečná
Je to nadbytečné, protože řádky, které budou vloženy, pokud je třeba je třídit, jsou stejně seřazeny.
Vytvořme testovací případ.
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;
Pojďme povolit textové zobrazení skutečných plánů dotazů, abychom mohli vidět, jaké úkoly provádí procesor dotazů.
SET STATISTICS PROFILE ON;
GO
Nyní pojďme INSERT
2K řádků do tabulky bez klauzule ORDER BY
.
INSERT INTO #Test
SELECT number
FROM #Sequence
Skutečný plán provádění tohoto dotazu je následující.
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]))
Jak vidíte, před skutečným VLOŽENÍM existuje operátor řazení.
Nyní vyjmeme tabulku a INSERT
2k řádků do tabulky s klauzulí ORDER BY
.
TRUNCATE TABLE #Test;
GO
INSERT INTO #Test
SELECT number
FROM #Sequence
ORDER BY number
Skutečný plán provádění tohoto dotazu je následující.
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]))
Všimněte si, že je to stejný prováděcí plán, jaký byl použit pro příkaz INSERT
bez klauzule ORDER BY
.
Nyní operace Sort
není vždy nutná jak ukázal Mark Smith v jiné odpovědi (pokud je počet řádků, které mají být vloženy, nízký), ale ORDER BY
klauzule je v tomto případě stále nadbytečná, protože ani při explicitní ORDER BY
není procesorem dotazu generována žádná operace Sort
.
Příkaz INSERT
můžete optimalizovat na tabulku se seskupeným indexem pomocí minimálně přihlášeného INSERT
, ale pro tuto otázku je to mimo rozsah.
Aktualizováno 2011-11-02: Jak ukázal Mark Smith , INSERT
s do tabulky s klastrem index nemusí vždy vyžadovat třídění - v tomto případě je však nadbytečná klauzule ORDER BY
.