it-swarm.dev

Dlaczego warto korzystać zarówno z TRUNCATE, jak i DROP?

W systemie, nad którym pracuję, jest wiele procedur przechowywanych i skryptów SQL, które korzystają z tabel tymczasowych. Po użyciu tych tabel dobrą praktyką jest ich upuszczanie.

Wielu moich kolegów (prawie wszyscy są o wiele bardziej doświadczeni niż ja) zazwyczaj to robi:

TRUNCATE TABLE #mytemp
DROP TABLE #mytemp

Zwykle używam pojedynczego DROP TABLE w moich skryptach.

Czy jest jakiś dobry powód, aby zrobić TRUNCATE bezpośrednio przed DROP?

102
user606723

Nr.

TRUNCATE i DROP są prawie identyczne pod względem zachowania i prędkości, więc wykonanie TRUNCATE tuż przed DROP jest po prostu niepotrzebne.


waga: napisałem tę odpowiedź z perspektywy SQL Server i założyłem, że będzie ona miała zastosowanie w równym stopniu do Sybase. Wygląda na to, że nie jest tak do końca .

waga: Kiedy po raz pierwszy opublikowałem tę odpowiedź, było kilka innych wysoko ocenianych odpowiedzi - w tym również odpowiedź zaakceptowana - które złożyły kilka fałszywych twierdzeń, takich jak: TRUNCATE nie jest rejestrowany; TRUNCATE nie można przywrócić; TRUNCATE jest szybszy niż DROP; itp.

Teraz, gdy ten wątek został oczyszczony, następujące obalenia mogą wydawać się styczne do pierwotnego pytania. Zostawiam je tutaj jako odniesienie dla innych, którzy chcą obalić te mity.


Istnieje kilka popularnych kłamstw - rozpowszechnionych nawet wśród doświadczonych DBA - które mogły być tego powodem TRUNCATE-then-DROP wzór. Oni są:

  • Mit : TRUNCATE nie jest rejestrowany, dlatego nie można go przywrócić.
  • Mit : TRUNCATE jest szybszy niż DROP.

Pozwól mi obalić te kłamstwa. Piszę to obalenie z perspektywy SQL Server, ale wszystko, co tu powiem, powinno mieć również zastosowanie do Sybase.

OBCIĄĆ is zalogowany, a can można przywrócić.

  • TRUNCATE jest zarejestrowaną operacją, więc to można wycofać . Po prostu zawiń to w transakcji.

    USE [tempdb];
    SET NOCOUNT ON;
    
    CREATE TABLE truncate_demo (
        whatever    VARCHAR(10)
    );
    
    INSERT INTO truncate_demo (whatever)
    VALUES ('log this');
    
    BEGIN TRANSACTION;
        TRUNCATE TABLE truncate_demo;
    ROLLBACK TRANSACTION;
    
    SELECT *
    FROM truncate_demo;
    
    DROP TABLE truncate_demo;
    

    Zauważ jednak, że jest to nieprawda dla Oracle . Chociaż zalogowany i chroniony przez funkcję cofania i ponawiania Oracle, TRUNCATE i inne instrukcje DDL nie można zostać wycofane przez użytkownika, ponieważ Oracle wydaje niejawne zobowiązania natychmiast przed i po wszystkich instrukcjach DDL.

  • TRUNCATE jest minimalnie zalogowany , a nie w pełni zalogowany. Co to znaczy? Powiedzmy, że TRUNCATE stół. Zamiast umieszczać każdy usunięty wiersz w dzienniku transakcji, TRUNCATE po prostu zaznacza strony danych, na których żyją, jako nieprzydzielone. Właśnie dlatego jest tak szybki. Dlatego też nie można odzyskać wierszy tabeli TRUNCATE z dziennika transakcji przy użyciu czytnika dziennika. Znajdziesz tam tylko odniesienia do stron zwolnionych danych.

    Porównaj to z DELETE. Jeśli DELETE wszystkie wiersze w tabeli i zatwierdzisz transakcję, nadal teoretycznie możesz znaleźć usunięte wiersze w dzienniku transakcji i odzyskać je stamtąd. Jest tak, ponieważ DELETE zapisuje każdy usunięty wiersz w dzienniku transakcji. W przypadku dużych tabel spowoduje to, że będzie on znacznie wolniejszy niż TRUNCATE.

UPUŚĆ jest równie szybki jak TRUNCATE.

  • Podobnie jak TRUNCATE, DROP jest operacją minimalnie rejestrowaną. Oznacza to, że DROP można wycofać też. Oznacza to również działa dokładnie tak samo jak TRUNCATE. Zamiast usuwać poszczególne wiersze, DROP oznacza odpowiednie strony danych jako nieprzydzielone i dodatkowo oznacza metadane tabeli jako usunięte .
  • Ponieważ TRUNCATE i DROP działają dokładnie w ten sam sposób, działają tak samo szybko. Nie ma sensu TRUNCATE - przechodzenie do tabeli przed DROP - pisanie jej. Uruchom this skrypt demo w twojej instancji programistycznej, jeśli mi nie wierzysz.

    Na mojej lokalnej maszynie z ciepłą pamięcią podręczną wyniki są następujące:

    table row count: 134,217,728
    
    run#        transaction duration (ms)
          TRUNCATE   TRUNCATE then DROP   DROP
    ==========================================
    01       0               1             4
    02       0              39             1
    03       0               1             1
    04       0               2             1
    05       0               1             1
    06       0              25             1
    07       0               1             1
    08       0               1             1
    09       0               1             1
    10       0              12             1
    ------------------------------------------
    avg      0              8.4           1.3
    

    Tak więc, dla tabeli 134 milionów wierszy, zarówno DROP, jak i TRUNCATE nie zajmują w ogóle czasu. (W przypadku zimnego bufora zajmują około 2-3 sekund dla pierwszego uruchomienia lub dwóch). Uważam również, że wyższy średni czas trwania operacji TRUNCATE, a następnie DROP, można przypisać zmianom obciążenia na moja lokalna maszyna i nie, ponieważ kombinacja jest w magiczny sposób o rząd wielkości gorsza niż poszczególne operacje. W końcu są prawie dokładnie tym samym.

    Jeśli interesują Cię bardziej szczegółowe informacje na temat narzutu związanego z logowaniem tych operacji, Martin ma proste wyjaśnienie tego.

132
Nick Chammas

Testowanie TRUNCATE, a następnie DROP w porównaniu z samym wykonaniem DROP bezpośrednio pokazuje, że pierwsze podejście faktycznie ma nieznacznie zwiększony narzut związany z logowaniem, więc może nawet być nieco nieproduktywne.

Patrząc na poszczególne rekordy dziennika pokazuje TRUNCATE ... DROP wersja jest prawie identyczna z wersją DROP z wyjątkiem tych dodatkowych wpisów.

+-----------------+---------------+-------------------------+
|    Operation    |    Context    |      AllocUnitName      |
+-----------------+---------------+-------------------------+
| LOP_COUNT_DELTA | LCX_CLUSTERED | sys.sysallocunits.clust |
| LOP_COUNT_DELTA | LCX_CLUSTERED | sys.sysrowsets.clust    |
| LOP_COUNT_DELTA | LCX_CLUSTERED | sys.sysrscols.clst      |
| LOP_COUNT_DELTA | LCX_CLUSTERED | sys.sysrscols.clst      |
| LOP_HOBT_DDL    | LCX_NULL      | NULL                    |
| LOP_MODIFY_ROW  | LCX_CLUSTERED | sys.sysallocunits.clust |
| LOP_HOBT_DDL    | LCX_NULL      | NULL                    |
| LOP_MODIFY_ROW  | LCX_CLUSTERED | sys.sysrowsets.clust    |
| LOP_LOCK_XACT   | LCX_NULL      | NULL                    |
+-----------------+---------------+-------------------------+

Tak więc pierwsza wersja TRUNCATE marnuje trochę wysiłku, dokonując niektórych aktualizacji różnych tabel systemowych w następujący sposób

  • Zaktualizuj rcmodified dla wszystkich kolumn tabeli w sys.sysrscols
  • Zaktualizuj rcrows w sysrowsets
  • Zero out pgfirst, pgroot, pgfirstiam, pcused, pcdata, pcreserved in sys.sysallocunits

Te wiersze tabeli systemowej zostają usunięte dopiero po upuszczeniu tabeli w następnej instrukcji.

Pełny podział rejestrowania przeprowadzonego przez TRUNCATE vs DROP znajduje się poniżej. Dodałem również DELETE w celach porównawczych.

+-------------------+-------------------+--------------------+------------------+-----------+---------------+-------------+------------------+-----------+---------------+-------------+
|                   |                   |                    |                            Bytes                           |                            Count                           |
+-------------------+-------------------+--------------------+------------------+-----------+---------------+-------------+------------------+-----------+---------------+-------------+
| Operation         | Context           | AllocUnitName      | Truncate / Drop  | Drop Only | Truncate Only | Delete Only | Truncate / Drop  | Drop Only | Truncate Only | Delete Only |
+-------------------+-------------------+--------------------+------------------+-----------+---------------+-------------+------------------+-----------+---------------+-------------+
| LOP_BEGIN_XACT    | LCX_NULL          |                    | 132              | 132       | 132           | 132         | 1                | 1         | 1             | 1           |
| LOP_COMMIT_XACT   | LCX_NULL          |                    | 52               | 52        | 52            | 52          | 1                | 1         | 1             | 1           |
| LOP_COUNT_DELTA   | LCX_CLUSTERED     | System Table       | 832              |           | 832           |             | 4                |           | 4             |             |
| LOP_DELETE_ROWS   | LCX_MARK_AS_GHOST | System Table       | 2864             | 2864      |               |             | 22               | 22        |               |             |
| LOP_DELETE_ROWS   | LCX_MARK_AS_GHOST | T                  |                  |           |               | 8108000     |                  |           |               | 1000        |
| LOP_HOBT_DDL      | LCX_NULL          |                    | 108              | 36        | 72            |             | 3                | 1         | 2             |             |
| LOP_LOCK_XACT     | LCX_NULL          |                    | 336              | 296       | 40            |             | 8                | 7         | 1             |             |
| LOP_MODIFY_HEADER | LCX_PFS           | Unknown Alloc Unit | 76               | 76        |               | 76          | 1                | 1         |               | 1           |
| LOP_MODIFY_ROW    | LCX_CLUSTERED     | System Table       | 644              | 348       | 296           |             | 5                | 3         | 2             |             |
| LOP_MODIFY_ROW    | LCX_IAM           | T                  | 800              | 800       | 800           |             | 8                | 8         | 8             |             |
| LOP_MODIFY_ROW    | LCX_PFS           | T                  | 11736            | 11736     | 11736         |             | 133              | 133       | 133           |             |
| LOP_MODIFY_ROW    | LCX_PFS           | Unknown Alloc Unit | 92               | 92        | 92            |             | 1                | 1         | 1             |             |
| LOP_SET_BITS      | LCX_GAM           | T                  | 9000             | 9000      | 9000          |             | 125              | 125       | 125           |             |
| LOP_SET_BITS      | LCX_IAM           | T                  | 9000             | 9000      | 9000          |             | 125              | 125       | 125           |             |
| LOP_SET_BITS      | LCX_PFS           | System Table       | 896              | 896       |               |             | 16               | 16        |               |             |
| LOP_SET_BITS      | LCX_PFS           | T                  |                  |           |               | 56000       |                  |           |               | 1000        |
| LOP_SET_BITS      | LCX_SGAM          | Unknown Alloc Unit | 168              | 224       | 168           |             | 3                | 4         | 3             |             |
+-------------------+-------------------+--------------------+------------------+-----------+---------------+-------------+------------------+-----------+---------------+-------------+
| Total             |                   |                    | 36736            | 35552     | 32220         | 8164260     | 456              | 448       | 406           | 2003        |
+-------------------+-------------------+--------------------+------------------+-----------+---------------+-------------+------------------+-----------+---------------+-------------+

Test został przeprowadzony w bazie danych z pełnym modelem odzysku dla tabeli z 1000 wierszami z jednym rzędem na stronę. Tabela zużywa łącznie 1004 strony ze względu na główną stronę indeksu i 3 strony indeksu poziomu pośredniego.

8 z tych stron to alokacje pojedynczych stron w mieszanych zakresach, a pozostała część rozłożona na 125 jednolitych zakresów. 8 pojedynczych anulowań przydziałów jest wyświetlanych jako 8 LOP_MODIFY_ROW,LCX_IAM wpisy w dzienniku. 125 dezalokacji zakresu jako LOP_SET_BITS LCX_GAM,LCX_IAM. Obie te operacje wymagają również aktualizacji powiązanej strony PFS, stąd połączone 133 LOP_MODIFY_ROW, LCX_PFS wpisy. Następnie, gdy tabela zostanie faktycznie upuszczona, należy usunąć metadane na jej temat z różnych tabel systemowych, stąd 22 tabela systemowa LOP_DELETE_ROWS wpisy do dziennika (rozliczone jak poniżej)

+----------------------+--------------+-------------------+-------------------+
|        Object        | Rows Deleted | Number of Indexes | Delete Operations |
+----------------------+--------------+-------------------+-------------------+
| sys.sysallocunits    |            1 |                 2 |                 2 |
| sys.syscolpars       |            2 |                 2 |                 4 |
| sys.sysidxstats      |            1 |                 2 |                 2 |
| sys.sysiscols        |            1 |                 2 |                 2 |
| sys.sysobjvalues     |            1 |                 1 |                 1 |
| sys.sysrowsets       |            1 |                 1 |                 1 |
| sys.sysrscols        |            2 |                 1 |                 2 |
| sys.sysschobjs       |            2 |                 4 |                 8 |
+----------------------+--------------+-------------------+-------------------+
|                      |              |                   |                22 |
+----------------------+--------------+-------------------+-------------------+

Pełny skrypt poniżej

DECLARE @Results TABLE
(
    Testing int NOT NULL,
    Operation nvarchar(31) NOT NULL,
    Context nvarchar(31)  NULL,
    AllocUnitName nvarchar(1000) NULL,
    SumLen int NULL,
    Cnt int NULL
)

DECLARE @I INT = 1

WHILE @I <= 4
BEGIN
IF OBJECT_ID('T','U') IS NULL
     CREATE TABLE T(N INT PRIMARY KEY,Filler char(8000) NULL)

INSERT INTO T(N)
SELECT DISTINCT TOP 1000 number
FROM master..spt_values


CHECKPOINT

DECLARE @allocation_unit_id BIGINT

SELECT @allocation_unit_id = allocation_unit_id
FROM   sys.partitions AS p
       INNER JOIN sys.allocation_units AS a
         ON p.hobt_id = a.container_id
WHERE  p.object_id = object_id('T')  

DECLARE @LSN NVARCHAR(25)
DECLARE @LSN_HEX NVARCHAR(25)

SELECT @LSN = MAX([Current LSN])
FROM fn_dblog(null, null)


SELECT @LSN_HEX=
        CAST(CAST(CONVERT(varbinary,SUBSTRING(@LSN, 1, 8),2) AS INT) AS VARCHAR) + ':' +
        CAST(CAST(CONVERT(varbinary,SUBSTRING(@LSN, 10, 8),2) AS INT) AS VARCHAR) + ':' +
        CAST(CAST(CONVERT(varbinary,SUBSTRING(@LSN, 19, 4),2) AS INT) AS VARCHAR)

  BEGIN TRAN
    IF @I = 1
      BEGIN
          TRUNCATE TABLE T

          DROP TABLE T
      END
    ELSE
      IF @I = 2
        BEGIN
            DROP TABLE T
        END
      ELSE
        IF @I = 3
          BEGIN
              TRUNCATE TABLE T
          END  
      ELSE
        IF @I = 4
          BEGIN
              DELETE FROM T
          END                
  COMMIT

INSERT INTO @Results
SELECT @I,
       CASE
         WHEN GROUPING(Operation) = 1 THEN 'Total'
         ELSE Operation
       END,
       Context,
       CASE
         WHEN AllocUnitId = @allocation_unit_id THEN 'T'
         WHEN AllocUnitName LIKE 'sys.%' THEN 'System Table'
         ELSE AllocUnitName
       END,
       COALESCE(SUM([Log Record Length]), 0) AS [Size in Bytes],
       COUNT(*)                              AS Cnt
FROM   fn_dblog(@LSN_HEX, null) AS D
WHERE  [Current LSN] > @LSN  
GROUP BY GROUPING SETS((Operation, Context,
       CASE
         WHEN AllocUnitId = @allocation_unit_id THEN 'T'
         WHEN AllocUnitName LIKE 'sys.%' THEN 'System Table'
         ELSE AllocUnitName
       END),())


SET @I+=1
END 

SELECT Operation,
       Context,
       AllocUnitName,
       AVG(CASE WHEN Testing = 1 THEN SumLen END) AS [Truncate / Drop Bytes],
       AVG(CASE WHEN Testing = 2 THEN SumLen END) AS [Drop Bytes],
       AVG(CASE WHEN Testing = 3 THEN SumLen END) AS [Truncate Bytes],
       AVG(CASE WHEN Testing = 4 THEN SumLen END) AS [Delete Bytes],
       AVG(CASE WHEN Testing = 1 THEN Cnt END) AS [Truncate / Drop Count],
       AVG(CASE WHEN Testing = 2 THEN Cnt END) AS [Drop Count],
       AVG(CASE WHEN Testing = 3 THEN Cnt END) AS [Truncate Count],
       AVG(CASE WHEN Testing = 4 THEN Cnt END) AS [Delete Count]              
FROM   @Results
GROUP  BY Operation,
          Context,
          AllocUnitName   
ORDER BY Operation, Context,AllocUnitName        

DROP TABLE T
52
Martin Smith

OK, pomyślałem, że spróbuję wykonać testy porównawcze, które nie polegałyby na „ciepłym buforowaniu”, więc mam nadzieję, że będą one bardziej realistycznym testem (także przy użyciu Postgres, aby sprawdzić, czy odpowiada tym samym cechom innych opublikowanych odpowiedzi) :

Moje testy porównawcze przy użyciu postgres 9.3.4 z dużą bazą danych (mam nadzieję, że wystarczająco duże, aby nie zmieściły się w pamięci podręcznej RAM):

Za pomocą tego skryptu testowego DB: https://Gist.github.com/rdp/8af84fbb54a430df8fc

z rzędami 10 mln:

truncate: 1763ms
drop: 2091ms
truncate + drop: 1763ms (truncate) + 300ms (drop) (2063ms total)
drop + recreate: 2063ms (drop) + 242ms (recreate)

z rzędami 100 mln:

truncate: 5516ms
truncate + drop: 5592ms
drop: 5680ms (basically, the exact same ballpark)

Na podstawie tego przypuszczam, że: drop jest „w przybliżeniu” tak szybki (lub szybszy) jak obcinanie + drop (przynajmniej w przypadku nowoczesnych wersji Postgres), jednak jeśli planujesz również odwrócić się i odtworzyć stół, możesz: dobrze trzymaj się prostego obcięcia, które jest szybsze niż upuszczanie + odtwarzanie (ma sens). FWIW.

uwaga 1: https://stackoverflow.com/questions/11419536/postgresql-truncation-speed/11423886#11423886 (mówi, że postgres 9.2 może mieć szybsze obcinanie niż poprzednie wersje). Jak zawsze porównuj z własnym systemem, aby zobaczyć jego cechy.

uwaga 2: Obcięcie można wycofać w postgresie, jeśli w transakcji: http://www.postgresql.org/docs/8.4/static/sql-truncate.html

uwaga 3: Obcinanie może przy małych tabelach czasem być wolniejsze niż usuwanie: https://stackoverflow.com/questions/11419536/postgresql-truncation-speed/11423886#11423886

2
rogerdpack

Dodanie perspektywy historycznej ...

Usunięcie tabeli wymaga zaktualizowania kilku tabel systemowych, co z kolei zwykle wymaga wprowadzenia zmian w tabeli systemowej w jednej transakcji (pomyśl „rozpocznij tran, usuń kolumny systemowe, usuń obiekty systemowe, zatwierdzaj”).

W „drop table” uwzględniono także potrzebę cofnięcia przydziału wszystkich stron danych/indeksów powiązanych z tabelą.

Wiele, wiele, wiele lat temu ... proces dezalokacji przestrzeni został uwzględniony w transakcji, która również zaktualizowała tabele systemowe; wynik netto był taki, że im większa liczba przydzielonych stron, tym dłużej trwało cofnięcie alokacji wspomnianych stron, tym dłużej transakcja (w tabelach systemowych) pozostała otwarta, a tym samym większa szansa na zablokowanie (w tabelach systemowych) innych procesów próbujących tworzyć/upuszczać tabele w tempdb (szczególnie nieprzyjemne w przypadku starszych stron == blokowanie na poziomie strony i potencjał dla tabeli - eskalacja blokady poziomu).

Jedną z wczesnych metod (stosowanych w tamtych czasach) w celu zmniejszenia rywalizacji o tabele systemowe było skrócenie czasu blokowania blokad w tabelach systemowych, a jednym (relatywnie) łatwym sposobem na to było cofnięcie przydziału stron danych/indeksów przed usunięciem stół.

Podczas truncate table nie dezalokuje wszystkie danych/stron indeksowych, dezalokuje wszystkie oprócz jednego 8-stronicowego zakresu (danych); innym „hackiem” było upuszczenie wszystkich indeksów przed upuszczeniem tabeli (tak, oddziel txn na sysindexes, ale mniejszy txn dla upuszczenia tabeli).

Kiedy weźmiesz pod uwagę, że (jeszcze wiele, wiele lat temu) istniała tylko jedna baza danych „tempdb”, a niektóre aplikacje zostały stworzone HEAVY użycie tego singla ” baza danych tempdb, wszelkie „włamania”, które mogłyby zmniejszyć rywalizację o tabele systemowe w „tempdb” były korzystne; z czasem sytuacja uległa poprawie ... wiele tymczasowych baz danych, blokowanie na poziomie wiersza tabel systemowych, lepsze metody dezalokacji itp.

W międzyczasie użycie truncate table nic nie zaszkodzi, jeśli pozostanie w kodzie.

1
markp-fuso