Dlaczego DELETE pozostawia trwały wpływ na wydajność?

20

Na końcu znajduje się skrypt testowy do porównywania wydajności między zmienną @table a tabelą #temp. Myślę, że skonfigurowałem to poprawnie - czasy działania są pobierane poza poleceniami DELETE / TRUNCATE. Wyniki, które otrzymuję są następujące (czasy w milisekundach).

@Table Variable  #Temp (delete)  #Temp (truncate)
---------------  --------------  ----------------
5723             5180            5506
15636            14746           7800
14506            14300           5583
14030            15460           5386
16706            16186           5360

Aby upewnić się, że jestem przy zdrowych zmysłach, pokazuje to, że CURRENT_TIMESTAMP (alias GetDate()) jest pobierany w czasie instrukcji, a nie partii, więc nie powinno być interakcji między TRUNCATE / DELETE z SET @StartTime = CURRENT_TIMESTAMPinstrukcją.

select current_timestamp
waitfor delay '00:00:04'
select current_timestamp

-----------------------
2012-10-21 11:29:20.290

-----------------------
2012-10-21 11:29:24.290

Jest to dość spójne w skoku między pierwszym i kolejnymi przebiegami, gdy DELETE służy do wyczyszczenia tabeli. Czego mi brakuje w rozumieniu DELETE ? Powtórzyłem to wiele razy, zamieniłem zamówienie, dostosowałem tempdb, aby nie wymagać wzrostu itp.

CREATE TABLE #values (
  id int identity primary key, -- will be clustered
  name varchar(100) null,
  number int null,
  type char(3) not null,
  low int null,
  high int null,
  status smallint not null
);
GO
SET NOCOUNT ON;

DECLARE @values TABLE (
  id int identity primary key clustered,
  name varchar(100) null,
  number int null,
  type char(3) not null,
  low int null,
  high int null,
  status smallint not null
);
DECLARE  @ExecutionTime  TABLE(      Duration bigINT    ) 
DECLARE  @StartTime DATETIME,  @i INT = 1; 
WHILE (@i <= 5) 
  BEGIN 
    DELETE @values;
    DBCC freeproccache With NO_InfoMSGS;
    DBCC DROPCLEANBUFFERS With NO_InfoMSGS;
    SET @StartTime = CURRENT_TIMESTAMP -- alternate getdate() 
    /****************** measured process ***********************/ 

    INSERT @values SELECT a.* FROM master..spt_values a join master..spt_values b on b.type='P' and b.number < 1000;

    /**************** end measured process *********************/ 
    INSERT @ExecutionTime 
    SELECT DurationInMilliseconds = datediff(ms,@StartTime,CURRENT_TIMESTAMP) 
    SET @i +=  1 
  END -- WHILE 

SELECT DurationInMilliseconds = Duration FROM   @ExecutionTime 
GO 

-- Temporary table
DECLARE  @ExecutionTime  TABLE(      Duration bigINT    ) 
DECLARE  @StartTime DATETIME,  @i INT = 1; 
WHILE (@i <= 5) 
  BEGIN 
    delete #values;
    -- TRUNCATE TABLE #values;
    DBCC freeproccache With NO_InfoMSGS;
    DBCC DROPCLEANBUFFERS With NO_InfoMSGS;
    SET @StartTime = CURRENT_TIMESTAMP -- alternate getdate() 
    /****************** measured process ***********************/ 

    INSERT #values SELECT a.* FROM master..spt_values a join master..spt_values b on b.type='P' and b.number < 1000;

    /**************** end measured process *********************/ 
    INSERT @ExecutionTime 
    SELECT DurationInMilliseconds = datediff(ms,@StartTime,CURRENT_TIMESTAMP) 
    SET @i +=  1 
  END -- WHILE 

SELECT DurationInMilliseconds = Duration FROM   @ExecutionTime 
GO

DROP TABLE  #values 
SET NOCOUNT OFF;
孔夫子
źródło

Odpowiedzi:

20

Ta różnica wydaje się obowiązywać tylko wtedy, gdy obiekt jest drzewem B +. Po usunięciu primary keyzmiennej on the table, która jest stertą, otrzymałem następujące wyniki

2560
2120
2080
2130
2140

Ale z PK znalazłem podobny wzór w moich testach, z typowymi wynikami poniżej.

+--------+--------+---------+-------------------+
| @table | #table | ##table | [permanent_table] |
+--------+--------+---------+-------------------+
|   2670 |   2683 |    9603 |              9703 |
|   6823 |   6840 |    9723 |              9790 |
|   6813 |   6816 |    9626 |              9703 |
|   6883 |   6816 |    9600 |              9716 |
|   6840 |   6856 |    9610 |              9673 |
+--------+--------+---------+-------------------+

Moja teoria jest taka, że ​​istnieje pewna optymalizacja przy wykonywaniu masowych wstawek do lokalnych tymczasowych drzew B +, która ma zastosowanie tylko wtedy, gdy nie ma jeszcze przydzielonych żadnych stron.

Opieram to na następujących spostrzeżeniach.

  1. Uruchamiając różne wersje kodu testowego widziałem tylko ten wzór @table_variablesi #temptabel. Nie są to stałe tabele tempdbani ##tabele.

  2. Aby uzyskać niższą wydajność, nie trzeba wcześniej dodawać i usuwać dużej liczby wierszy z tabeli. Wystarczy dodać pojedynczy wiersz i pozostawić go tam.

  3. TRUNCATEzwalnia wszystkie strony z tabeli. DELETEnie spowoduje cofnięcia przydziału ostatniej strony w tabeli.

  4. Korzystanie z profilera VS 2012 pokazuje, że w szybszym przypadku SQL Server używa innej ścieżki kodu. 36% czasu spędza w sqlmin.dll!RowsetBulk::InsertRowporównaniu do 61% czasu przeznaczonego sqlmin.dll!RowsetNewSS::InsertRowna wolniejszy przypadek.

Bieganie

SELECT * 
FROM sys.dm_db_index_physical_stats(2,OBJECT_ID('tempdb..#values'),1,NULL, 'DETAILED')

po powrocie usunięcia

+-------------+------------+--------------+--------------------+
| index_level | page_count | record_count | ghost_record_count |
+-------------+------------+--------------+--------------------+
|           0 |          1 |            0 |                  1 |
|           1 |          1 |            1 |                  0 |
|           2 |          1 |            1 |                  0 |
+-------------+------------+--------------+--------------------+

Odkryłem, że można nieco zmniejszyć rozbieżność czasu, włączając flagę śledzenia 610 .

To miało wpływ na zmniejszenie ilości rejestrowania zasadniczo na kolejnych wkładek (spadek od 350 MB do 103 MB, ponieważ nie rejestruje poszczególne wstawione wartości wiersz), ale ma tylko poprawę niewielki synchronizacji dla 2 i kolejnych @table, #tableprzypadkach a różnica wciąż pozostaje. Znacznik śledzenia znacznie poprawił ogólną wydajność wstawek do pozostałych dwóch typów tabel.

+--------+--------+---------+-------------------+
| @table | #table | ##table | [permanent_table] |
+--------+--------+---------+-------------------+
|   2663 |   2670 |    5403 |              5426 |
|   5390 |   5396 |    5410 |              5403 |
|   5373 |   5390 |    5410 |              5403 |
|   5393 |   5410 |    5406 |              5433 |
|   5386 |   5396 |    5390 |              5420 |
+--------+--------+---------+-------------------+

Patrząc w dziennik transakcji, zauważyłem, że początkowe wstawki dla pustych lokalnych tabel tymczasowych wydają się jeszcze bardziej minimalnie zarejestrowane (przy 96 MB).

W szczególności te szybsze wstawki miały tylko 657transakcje ( LOP_BEGIN_XACT/ LOP_COMMIT_XACTpary) w porównaniu z ponad 10,000w wolniejszych przypadkach. W szczególności LOP_FORMAT_PAGEoperacje wydają się znacznie ograniczone. Wolniejsze sprawy mają dla tego wpisu dziennika transakcji dla każdej strony w tabeli (około 10,270) w porównaniu z tylko 4takimi wpisami w przypadku szybkim.

Dziennik używany we wszystkich trzech przypadkach był następujący (usunąłem zapisy dziennika dotyczące aktualizacji tabel bazowych systemu, aby zmniejszyć ilość tekstu, ale nadal są one uwzględniane w sumach)

Logowanie do pierwszej wkładki @table_var(96,5 MB)

+-----------------------+----------+----------------------------------------------+---------------+---------+
|       Operation       | Context  |                AllocUnitName                 | Size in Bytes |   Cnt   |
+-----------------------+----------+----------------------------------------------+---------------+---------+
| LOP_BEGIN_XACT        | LCX_NULL | NULL                                         |         83876 |     658 |
| LOP_COMMIT_XACT       | LCX_NULL | NULL                                         |         34164 |     657 |
| LOP_CREATE_ALLOCCHAIN | LCX_NULL | NULL                                         |           120 |       3 |
| LOP_FORMAT_PAGE       | LCX_HEAP | dbo.#531856C7                                |            84 |       1 |
| LOP_FORMAT_PAGE       | LCX_IAM  | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |            84 |       1 |
| LOP_FORMAT_PAGE       | LCX_IAM  | dbo.#531856C7                                |            84 |       1 |
| LOP_FORMAT_PAGE       | LCX_IAM  | Unknown Alloc Unit                           |            84 |       1 |
| LOP_HOBT_DDL          | LCX_NULL | NULL                                         |           216 |       6 |
| LOP_HOBT_DELTA        | LCX_NULL | NULL                                         |           320 |       5 |
| LOP_IDENT_NEWVAL      | LCX_NULL | NULL                                         |     100240000 | 2506000 |
| LOP_INSERT_ROWS       | LCX_HEAP | dbo.#531856C7                                |            72 |       1 |
| LOP_MODIFY_ROW        | LCX_IAM  | dbo.#531856C7                                |            88 |       1 |
| LOP_MODIFY_ROW        | LCX_PFS  | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |        158592 |    1848 |
| LOP_MODIFY_ROW        | LCX_PFS  | dbo.#531856C7                                |            80 |       1 |
| LOP_MODIFY_ROW        | LCX_PFS  | Unknown Alloc Unit                           |        216016 |    2455 |
| LOP_SET_BITS          | LCX_GAM  | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |         84360 |    1406 |
| LOP_SET_BITS          | LCX_GAM  | Unknown Alloc Unit                           |        147120 |    2452 |
| LOP_SET_BITS          | LCX_IAM  | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |         84360 |    1406 |
| LOP_SET_BITS          | LCX_IAM  | Unknown Alloc Unit                           |        147120 |    2452 |
| Total                 | NULL     | NULL                                         |     101209792 | 2519475 |
+-----------------------+----------+----------------------------------------------+---------------+---------+

Logowanie kolejnych wstawek TF 610 wyłączone (350 MB)

+-----------------------+--------------------+----------------------------------------------+---------------+---------+
|       Operation       |      Context       |                AllocUnitName                 | Size in Bytes |   Cnt   |
+-----------------------+--------------------+----------------------------------------------+---------------+---------+
| LOP_BEGIN_CKPT        | LCX_NULL           | NULL                                         |            96 |       1 |
| LOP_BEGIN_XACT        | LCX_NULL           | NULL                                         |       1520696 |   12521 |
| LOP_COMMIT_XACT       | LCX_NULL           | NULL                                         |        651040 |   12520 |
| LOP_CREATE_ALLOCCHAIN | LCX_NULL           | NULL                                         |            40 |       1 |
| LOP_DELETE_SPLIT      | LCX_INDEX_INTERIOR | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |          2160 |      36 |
| LOP_END_CKPT          | LCX_NULL           | NULL                                         |           136 |       1 |
| LOP_FORMAT_PAGE       | LCX_HEAP           | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |        859236 |   10229 |
| LOP_FORMAT_PAGE       | LCX_IAM            | Unknown Alloc Unit                           |            84 |       1 |
| LOP_FORMAT_PAGE       | LCX_INDEX_INTERIOR | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |          3108 |      37 |
| LOP_HOBT_DDL          | LCX_NULL           | NULL                                         |           648 |      18 |
| LOP_HOBT_DELTA        | LCX_NULL           | NULL                                         |        657088 |   10267 |
| LOP_IDENT_NEWVAL      | LCX_NULL           | NULL                                         |     100239960 | 2505999 |
| LOP_INSERT_ROWS       | LCX_CLUSTERED      | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |     258628000 | 2506000 |
| LOP_INSERT_ROWS       | LCX_HEAP           | dbo.#531856C7                                |            72 |       1 |
| LOP_INSERT_ROWS       | LCX_INDEX_INTERIOR | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |       1042776 |   10302 |
| LOP_MODIFY_HEADER     | LCX_HEAP           | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |        859236 |   10229 |
| LOP_MODIFY_HEADER     | LCX_INDEX_INTERIOR | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |          3192 |      38 |
| LOP_MODIFY_ROW        | LCX_IAM            | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |           704 |       8 |
| LOP_MODIFY_ROW        | LCX_PFS            | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |        934264 |   11550 |
| LOP_MODIFY_ROW        | LCX_PFS            | Unknown Alloc Unit                           |        783984 |    8909 |
| LOP_SET_BITS          | LCX_GAM            | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |         76980 |    1283 |
| LOP_SET_BITS          | LCX_GAM            | Unknown Alloc Unit                           |        534480 |    8908 |
| LOP_SET_BITS          | LCX_IAM            | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |         76980 |    1283 |
| LOP_SET_BITS          | LCX_IAM            | Unknown Alloc Unit                           |        534480 |    8908 |
| LOP_SHRINK_NOOP       | LCX_NULL           | NULL                                         |            32 |       1 |
| LOP_XACT_CKPT         | LCX_NULL           | NULL                                         |            92 |       1 |
| Total                 | NULL               | NULL                                         |     367438748 | 5119297 |
+-----------------------+--------------------+----------------------------------------------+---------------+---------+

Logowanie kolejnych wstawek TF 610 na (103 MB)

+-------------------------+-------------------------+----------------------------------------------+---------------+---------+
|        Operation        |         Context         |                AllocUnitName                 | Size in Bytes |   Cnt   |
+-------------------------+-------------------------+----------------------------------------------+---------------+---------+
| LOP_BEGIN_CKPT          | LCX_NULL                | NULL                                         |           192 |       2 |
| LOP_BEGIN_XACT          | LCX_NULL                | NULL                                         |       1339796 |   11099 |
| LOP_BULK_EXT_ALLOCATION | LCX_NULL                | NULL                                         |         20616 |     162 |
| LOP_COMMIT_XACT         | LCX_NULL                | NULL                                         |        577096 |   11098 |
| LOP_CREATE_ALLOCCHAIN   | LCX_NULL                | NULL                                         |            40 |       1 |
| LOP_DELETE_SPLIT        | LCX_INDEX_INTERIOR      | dbo.#6DCC4D03.PK__#6DCC4D0__3213E83F6FB49575 |          2160 |      36 |
| LOP_END_CKPT            | LCX_NULL                | NULL                                         |           272 |       2 |
| LOP_FORMAT_PAGE         | LCX_BULK_OPERATION_PAGE | dbo.#6DCC4D03.PK__#6DCC4D0__3213E83F6FB49575 |        863520 |   10280 |
| LOP_FORMAT_PAGE         | LCX_IAM                 | Unknown Alloc Unit                           |            84 |       1 |
| LOP_FORMAT_PAGE         | LCX_INDEX_INTERIOR      | dbo.#6DCC4D03.PK__#6DCC4D0__3213E83F6FB49575 |          3108 |      37 |
| LOP_HOBT_DELTA          | LCX_NULL                | NULL                                         |        666496 |   10414 |
| LOP_IDENT_NEWVAL        | LCX_NULL                | NULL                                         |     100239960 | 2505999 |
| LOP_INSERT_ROWS         | LCX_CLUSTERED           | dbo.#6DCC4D03.PK__#6DCC4D0__3213E83F6FB49575 |         23544 |     218 |
| LOP_INSERT_ROWS         | LCX_HEAP                | dbo.#719CDDE7                                |            72 |       1 |
| LOP_INSERT_ROWS         | LCX_INDEX_INTERIOR      | dbo.#6DCC4D03.PK__#6DCC4D0__3213E83F6FB49575 |       1042776 |   10302 |
| LOP_MODIFY_HEADER       | LCX_BULK_OPERATION_PAGE | dbo.#6DCC4D03.PK__#6DCC4D0__3213E83F6FB49575 |        780216 |   10266 |
| LOP_MODIFY_HEADER       | LCX_HEAP                | dbo.#6DCC4D03.PK__#6DCC4D0__3213E83F6FB49575 |       1718472 |   20458 |
| LOP_MODIFY_HEADER       | LCX_INDEX_INTERIOR      | dbo.#6DCC4D03.PK__#6DCC4D0__3213E83F6FB49575 |          3192 |      38 |
| LOP_MODIFY_ROW          | LCX_IAM                 | dbo.#6DCC4D03.PK__#6DCC4D0__3213E83F6FB49575 |           704 |       8 |
| LOP_MODIFY_ROW          | LCX_PFS                 | dbo.#6DCC4D03.PK__#6DCC4D0__3213E83F6FB49575 |        114832 |    1307 |
| LOP_MODIFY_ROW          | LCX_PFS                 | Unknown Alloc Unit                           |        231696 |    2633 |
| LOP_RANGE_INSERT        | LCX_NULL                | NULL                                         |            48 |       1 |
| LOP_SET_BITS            | LCX_GAM                 | dbo.#6DCC4D03.PK__#6DCC4D0__3213E83F6FB49575 |         77100 |    1285 |
| LOP_SET_BITS            | LCX_GAM                 | Unknown Alloc Unit                           |        157920 |    2632 |
| LOP_SET_BITS            | LCX_IAM                 | dbo.#6DCC4D03.PK__#6DCC4D0__3213E83F6FB49575 |         77100 |    1285 |
| LOP_SET_BITS            | LCX_IAM                 | Unknown Alloc Unit                           |        157920 |    2632 |
| LOP_XACT_CKPT           | LCX_NULL                | NULL                                         |            92 |       1 |
| Total                   | NULL                    | NULL                                         |     108102960 | 2602218 |
+-------------------------+-------------------------+----------------------------------------------+---------------+---------+
Martin Smith
źródło
Dziękuję za szczegółowe potwierdzenie. Więc pytanie wciąż się zastanawia, dlaczego DELETE nie zwraca tabeli do prawdziwego opróżnienia, używając twojego terminu. Argumentowałby to również za użyciem tabel #temp, jeśli w pętli przetwarzania wsadowego użyto opcji wyczyść / zapełnij.
孔夫子
1
@RichardTheKiwi - Korzyść z TRUNCATEponad DELETEna własną rękę również argumentować za to. Rzadko rozważałbym również zmienne tabeli dla dużej liczby wierszy.
Martin Smith
To zabrzmi leniwie, ale czy powtórzenie 1000 do 10-rekordowej (zmiennej) wstawki 1000 razy w partii nie będzie miało takich samych objawów? Zastosowanie dużej liczby wierszy służy jedynie do zaostrzenia problemu i zapewnienia skali, aby lepiej dostrzec różnicę. Istota pytania polega na tym, aby udowodnić w ten czy inny sposób, że tabele #temp byłyby lepsze, gdy tylko wiemy, na czym polega różnica.
孔夫子
Cóż, moja teoria jest taka, że ​​przydzielanie 10,000+stron odbywa się w znacznie bardziej zoptymalizowany sposób i wydaje się, że unika się narzutu na stronę. W przypadku mniejszych wkładek spodziewałbym się, że każda taka różnica będzie mniej znacząca.
Martin Smith
@RichardTheKiwi - Dzięki! Prawdopodobnie jest więcej do powiedzenia na ten temat. Ponieważ spróbuję uaktualnić do tej samej wersji co SQL Kiwi i sprawdzić, czy nadal widzę różne ścieżki kodu. Jeśli tak, to może to zależy od sprzętu, że robi to różnicę (moje testy były na moim komputerze stacjonarnym ze wszystkimi danymi i plikami dziennika na tym samym dysku SSD)
Martin Smith
0

Obserwacja i spekulacje. . .

W niektórych systemach CURRENT_TIMESTAMP jest zdefiniowany jako czas na początku bieżącej transakcji. Szybkie wyszukiwanie nie wykazało ostatecznej dokumentacji zachowania CURRENT_TIMESTAMP na SQL Server. Ale domyślnym trybem SQL Server jest automatyczne zatwierdzanie transakcji i nie ma tutaj POCZĄTKUJĄCEJ TRANSAKCJI, więc powinien to być czas bezpośrednio przed instrukcją INSERT. (Instrukcja DELETE powinna automatycznie zatwierdzać, a niezależnie od tego, w jaki sposób CURRENT_TIMESTAMP działa na SQL Server, nie powinna mieć nic wspólnego z instrukcją DELETE, gdy korzystasz z transakcji automatycznie zatwierdzanych).

Podczas pierwszej iteracji instrukcja DELETE nie ma żadnej pracy do wykonania i nie ma żadnych pojedynczych wierszy do zarejestrowania. Być może optymalizator wie o tym, a to skraca czas pierwszej iteracji. (Kombinacja braku wierszy do usunięcia i brak pojedynczych wierszy do zarejestrowania).

Można to przetestować (myślę), wstawiając przed usunięciem.

Mike Sherrill „Cat Recall”
źródło
Dzisiaj przestanę odpowiadać na pytania. Lub cokolwiek robię, kiedy wpisuję rzeczy w tym polu.
Mike Sherrill „Cat Recall”
Czy tę odpowiedź należy usunąć jako przestarzałą, styczną i rozpraszającą?
孔夫子