it-swarm.dev

Wydajne WSTAWIANIE DO tabeli o indeksie klastrowym

Mam instrukcję SQL, która wstawia wiersze do tabeli z indeksem klastrowym w kolumnie TRACKING_NUMBER.

NA PRZYKŁAD.:

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

Moje pytanie brzmi - czy pomaga w użyciu klauzuli ORDER BY w instrukcji SELECT dla kolumny klastrowanego indeksu, czy też uzyskany zysk zostałby zanegowany przez dodatkowe sortowanie wymagane dla klauzuli ORDER BY?

29
GWR

Ponieważ inne odpowiedzi już wskazują, że SQL Server może, ale nie musi, jawnie sortować wiersze w indeksie klastrowym przed insert.

Zależy to od tego, czy operator indeksu klastrowego w planie ma ustawioną właściwość DMLRequestSort (która z kolei zależy od szacunkowej liczby wstawianych wierszy).

Jeśli okaże się, że SQL Server nie docenia tego z jakiegokolwiek powodu, możesz skorzystać z dodania jawnego ORDER BY do zapytania SELECT, aby zminimalizować podziały stron i wynikające z tego fragmentacje z operacji INSERT

Przykład:

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

Pokazuje, że T jest bardzo rozdrobniony

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 dla T2 fragmentacja jest minimalna

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

I odwrotnie, czasami możesz chcieć zmusić SQL Server do niedoceniania liczby wierszy, gdy wiesz, że dane są już wstępnie posortowane i chcesz uniknąć niepotrzebnego sortowania. Jednym z godnych uwagi przykładów jest wstawianie dużej liczby wierszy do tabeli za pomocą klastrowego klucza indeksu newsequentialid. W wersjach SQL Server wcześniejszych niż Denali SQL Server dodaje niepotrzebną i potencjalnie kosztowną operację sortowania . Można tego uniknąć

DECLARE @var INT =2147483647

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

SQL Server oszacuje następnie, że zostanie wstawionych 100 wierszy bez względu na rozmiar Bar, który jest poniżej progu, przy którym sortowanie jest dodawane do planu. Jednak, jak wskazano w komentarzach poniżej, oznacza to, że wkładka niestety nie będzie mogła skorzystać z minimalnego logowania.

18
Martin Smith

Jeśli optymalizator zdecyduje, że bardziej wydajne byłoby sortowanie danych przed wstawieniem, zrobi to gdzieś przed operatorem wstawiania. Jeśli wprowadzisz sortowanie jako część zapytania, optymalizator powinien zdać sobie sprawę z tego, że dane są już posortowane i pominąć robienie tego ponownie. Uwaga: wybrany plan wykonania może się różnić w zależności od liczby wierszy wstawianych z tabeli pomostowej.

Jeśli możesz uchwycić plany wykonania procesu z jawnym sortowaniem i bez niego, dołącz je do pytania w celu komentarza.

Edycja: 28.10.2011 17:00

@ Odpowiedź Gonsal wydaje się pokazywać, że zawsze występuje operacja sortowania, tak nie jest. Wymagane skrypty demonstracyjne!

Ponieważ skrypty stały się dość duże, przeniosłem je na Gist . Aby ułatwić eksperymentowanie, skrypty używają trybu SQLCMD. Testy przeprowadzane są na 2K5SP3, dwurdzeniowy, 8 GB.

Testy wstawek obejmują trzy scenariusze:

  1. Indeks klastrowany danych pomostowych w tej samej kolejności co cel.
  2. Indeks klastrowany danych pomostowych w odwrotnej kolejności.
  3. Dane pomostowe zgrupowane w col2, które zawierają losową INT.

Pierwsze uruchomienie, wstawienie 25 rzędów.

1st run, 25 rows

Wszystkie trzy plany wykonania są takie same, nigdzie w planie nie występuje sortowanie, a skanowanie indeksu klastrowego jest „uporządkowane = fałszywe”.

Drugi przebieg, wstawienie 26 wierszy.

2nd run, 26 rows

Tym razem plany się różnią.

  • Pierwszy pokazuje skanowanie indeksu klastrowanego zgodnie z kolejnością = fałsz. Nie nastąpiło sortowanie, ponieważ dane źródłowe są odpowiednio posortowane.
  • W drugim skrypcie indeks klastrowany skanuje zgodnie z kolejnością = true, wstecz. Nie mamy więc operacji sortowania, ale optymalizacja rozpoznaje potrzebę sortowania danych i skanuje je w odwrotnej kolejności.
  • Trzeci pokazuje operator sortowania.

Jest więc punkt zwrotny, w którym optymalizator uważa, że ​​konieczne jest zastosowanie pewnego rodzaju. Jak pokazuje @MartinSmith, wydaje się, że jest to oparte na szacowanych wierszach do wstawienia. Na moim urządzeniu testowym 25 nie wymaga sortowania, 26 wymaga (2K5SP3, dwurdzeniowy, 8 GB)

Skrypt SQLCMD zawiera zmienne, które pozwalają na zmianę wielkości wierszy w tabeli (zmieniając gęstość strony) i liczbę wierszy w dbo.MyTable przed dodatkowymi wstawieniami. Z moich testów nie ma żadnego wpływu na punkt krytyczny.

Jeśli któryś z czytelników jest tak skłonny, proszę ruchom skrypty i dodaj swój punkt zwrotny jako komentarz. Chciałbym usłyszeć, czy różni się w zależności od zestawu testowego i/lub wersji.

Edycja: 28.10.2011 20:15

Powtórzone testy na tym samym urządzeniu, ale z 2K8R2. Tym razem punkt krytyczny wynosi 251 rzędów. Ponownie, zmiana gęstości strony i liczby istniejących wierszy nie ma wpływu.

12
Mark Storey-Smith

Klauzula ORDER BY W instrukcji SELECT jest zbędna.

Jest zbędny, ponieważ wiersze, które zostaną wstawione, jeśli trzeba je posortować, i tak są posortowane.

Utwórzmy przypadek testowy.

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;

Włączmy wyświetlanie tekstowe rzeczywistych planów zapytań, abyśmy mogli zobaczyć, jakie zadania są wykonywane przez procesor zapytań.

SET STATISTICS PROFILE ON;
GO

Teraz, INSERT 2K wierszy w tabeli bez klauzuli ORDER BY.

INSERT INTO #Test
SELECT number
  FROM #Sequence

Rzeczywisty plan wykonania tego zapytania jest następujący.

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 widać, przed rzeczywistym wstawieniem jest operator sortowania.

Teraz wyczyśćmy tabelę i INSERT 2k wierszy do tabeli z klauzulą ​​ORDER BY.

TRUNCATE TABLE #Test;
GO

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

Rzeczywisty plan wykonania tego zapytania jest następujący.

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]))

Zauważ, że jest to ten sam plan wykonania, który został użyty dla instrukcji INSERT bez klauzuli ORDER BY.

Teraz operacja Sort nie zawsze jest wymagana, jak pokazał Mark Smith w innej odpowiedzi (jeśli liczba wstawianych wierszy jest niska), ale ORDER BY klauzula nadal jest w tym przypadku zbędna, ponieważ nawet przy jawnym ORDER BY, procesor zapytań nie generuje żadnej operacji Sort.

Możesz zoptymalizować instrukcję INSERT w tabeli z indeksem klastrowym, używając minimalnie zalogowanego INSERT, ale to nie wchodzi w zakres tego pytania.

Zaktualizowano 2011-11-02: jak pokazał Mark Smith , INSERTs do tabeli z klastrami indeks może nie zawsze wymagać sortowania - klauzula ORDER BY również jest w tym przypadku zbędna.

8
gonsalu