it-swarm-eu.dev

Efektivní VLOŽTE DO tabulky se seskupeným indexem

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?

29
GWR

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í.

18
Martin Smith

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:

  1. Index skupinových dat seskupený ve stejném pořadí jako cíl.
  2. Index skupinových dat seskupených v obráceném pořadí.
  3. Pracovní data seskupená podle col2, která obsahuje náhodné INT.

První spuštění, vložení 25 řádků.

1st run, 25 rows

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ů.

2nd run, 26 rows

Tentokrát se plány liší.

  • První ukazuje skenování seskupeného indexu podle objednávky = false. Nedocházelo k žádnému třídění, protože zdrojová data jsou vhodně tříděna.
  • Ve druhém skenování seskupeného indexu podle objednávky = true, zpět. Nemáme tedy operaci třídění, ale optimalizace rozeznává potřebu třídění dat a skenuje v opačném pořadí.
  • Třetí ukazuje operátora řazení.

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.

12
Mark Storey-Smith

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 , INSERTs 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.

8
gonsalu