it-swarm-eu.dev

Proč používat TRUNCATE i DROP?

V systému, na kterém pracuji, je spousta uložených procedur a skriptů SQL, které využívají dočasné tabulky. Po použití těchto tabulek je vhodné je zrušit.

Mnoho mých kolegů (téměř všichni, kteří mají mnohem zkušenější zkušenosti než já), to obvykle dělají:

TRUNCATE TABLE #mytemp
DROP TABLE #mytemp

Obvykle používám jeden DROP TABLE v mých skriptech.

Existuje nějaký dobrý důvod k provedení TRUNCATE bezprostředně před DROP?

102
user606723

Č.

TRUNCATE a DROP jsou téměř identické v chování a rychlosti, takže dělat TRUNCATE těsně před DROP je jednoduše zbytečné.


Poznámka: Tuto odpověď jsem napsal z perspektivy serveru SQL a předpokládal, že by se vztahoval stejně na Sybase. Zdá se, že není tomu tak úplně .

Poznámka: Když jsem poprvé zveřejnil tuto odpověď, objevilo se několik dalších vysoce hodnocených odpovědí - včetně tehdy přijaté odpovědi -, která vydala několik nepravdivých tvrzení, jako například: TRUNCATE není přihlášen; TRUNCATE nelze vrátit zpět; TRUNCATE je rychlejší než DROP; atd.

Nyní, když bylo toto vlákno vyčištěno, se může zdát, že následující vyvrácení se dotýká původní otázky. Nechám je zde jako referenci pro ostatní, kteří se snaží tyto mýty odhalit.


Existuje několik populárních klamů - všudypřítomných i mezi zkušenými DBA - které to možná motivovaly TRUNCATE-then-DROP vzor. Oni jsou:

  • Mýtus : TRUNCATE není přihlášen, proto jej nelze vrátit zpět.
  • Mýtus : TRUNCATE je rychlejší než DROP.

Dovolte mi tyto nepravdy vyvrátit. Píšu toto vyvrácení z pohledu serveru SQL, ale vše, co zde říkám, by mělo být stejně použitelné pro Sybase.

ZKRÁCENO je zaznamenáno a lze jej vrátit zadní.

  • TRUNCATE je protokolovaná operace, takže it lze vrátit zpět . Stačí to zabalit do transakce.

    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;
    

    Všimněte si však, že toto je neplatí pro Oracle . Přestože jsou protokoly a chráněny funkcemi vrácení a opakování Oracle, TRUNCATE a další příkazy DDL nemohou být uživatelem vráceny zpět, protože Oracle vydává implicitní) zavazuje se bezprostředně před a po všech výpisech DDL.

  • TRUNCATE je minimálně protokolováno , na rozdíl od plně přihlášeného. Co to znamená? Řekněme, že TRUNCATE tabulku. Namísto vložení každého odstraněného řádku do protokolu transakcí TRUNCATE pouze označí datové stránky, na nichž žijí, jako nepřidělené. Proto je to tak rychlé. To je také důvod, proč nemůžete obnovit řádky tabulky TRUNCATE- ed z protokolu transakcí pomocí čtečky protokolů. Vše, co najdete, jsou odkazy na vyhrazené datové stránky.

    Porovnejte to s DELETE. Pokud DELETE všechny řádky v tabulce a potvrzení transakce, můžete teoreticky najít odstraněné řádky v protokolu transakcí a obnovit je odtud. Je to proto, že DELETE zapisuje každý smazaný řádek do protokolu transakcí. U velkých tabulek to bude mnohem pomalejší než TRUNCATE.

DROP je stejně rychlý jako TRUNCATE.

  • Stejně jako TRUNCATE, DROP je minimálně protokolovaná operace. To znamená, že DROP lze vrátit zpět také. To také znamená funguje to úplně stejným způsobem jako TRUNCATE. Místo odstranění jednotlivých řádků DROP označí příslušné datové stránky jako nepřidělené a navíc označí metadata tabulky jako smazaná .
  • Protože TRUNCATE a DROP pracují přesně stejným způsobem, běží stejně rychle jako jeden druhého. Nemá smysl TRUNCATE-ing tabulku před DROP- ing. Run this demo skript ve vaší vývojové instanci, pokud mi nevěříte.

    Na mém místním počítači s teplou mezipamětí jsou výsledky následující:

    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že pro tabulku 134 milionů řádků nezabírají DROP a TRUNCATE vůbec. (Na studené mezipaměti zabírají asi 2–3 sekundy při prvním spuštění nebo dvou.) Také se domnívám, že vyšší průměrné trvání operace TRUNCATE pak DROP lze připsat odchylkám zatížení na můj místní počítač a ne , protože kombinace je nějak magicky o řád horší než jednotlivé operace. Konec konců jsou téměř úplně stejné.

    Pokud vás zajímají podrobnější informace o režii protokolování těchto operací, Martin má přímé vysvětlení .

132
Nick Chammas

Testování TRUNCATE potom DROP vs pouze provedení DROP přímo ukazuje, že první přístup má ve skutečnosti mírně zvýšenou režii protokolování, takže může být dokonce mírně kontraproduktivní.

Při pohledu na jednotlivé záznamy protokolu se zobrazí TRUNCATE ... DROP verze je téměř totožná s verzí DROP s výjimkou těchto dalších položek.

+-----------------+---------------+-------------------------+
|    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                    |
+-----------------+---------------+-------------------------+

První verze TRUNCATE tedy končí plýtváním trochou úsilí provedením některých aktualizací různých systémových tabulek následovně

  • Aktualizace rcmodified pro všechny sloupce tabulky v sys.sysrscols
  • Aktualizace rcrows v sysrowsets
  • Nulová hodnota pgfirst, pgroot, pgfirstiam, pcused, pcdata, pcreserved in sys.sysallocunits

Tyto řádky systémové tabulky budou odstraněny až poté, co bude tabulka v dalším příkazu vynechána.

Úplné rozdělení protokolování provedené TRUNCATE vs DROP je níže. Pro účely srovnání jsem také přidal DELETE.

+-------------------+-------------------+--------------------+------------------+-----------+---------------+-------------+------------------+-----------+---------------+-------------+
|                   |                   |                    |                            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 byl proveden v databázi s úplným modelem obnovy oproti 1000 řádkové tabulce s jedním řádkem na stránku. Tabulka spotřebuje celkem 1 004 stránek v důsledku kořenové indexové stránky a 3 indexových stránek střední úrovně.

8 z těchto stránek jsou alokace jedné stránky ve smíšeném rozsahu a zbytek je distribuován do 125 jednotných rozsahů. 8 alokací jedné stránky se zobrazí jako 8 LOP_MODIFY_ROW,LCX_IAM položky protokolu. Deallokace rozsahu 125 jako LOP_SET_BITS LCX_GAM,LCX_IAM. Obě tyto operace také vyžadují aktualizaci přidružené stránky PFS, takže kombinovaná 133 LOP_MODIFY_ROW, LCX_PFS záznamy. Poté, když je tabulka skutečně vynechána, je třeba odstranit metadata z různých systémových tabulek, a tedy 22 systémových tabulek LOP_DELETE_ROWS položky protokolu (účtováno jako níže)

+----------------------+--------------+-------------------+-------------------+
|        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 |
+----------------------+--------------+-------------------+-------------------+

Úplný skript níže

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 si myslel, že se pokusím udělat několik benchmarků, které se nespoléhaly na žádné „teplé cacheování“, takže snad budou realističtějším testem (také pomocí Postgresu, abychom zjistili, zda odpovídá stejným charakteristikám jako ostatní vyslané odpovědi) :

Moje měřítka používající postgres 9.3.4 s rozsáhlou databází (doufejme, že jsou dostatečně velké, aby se vešly do RAM mezipaměti):

Pomocí tohoto testovacího skriptu DB: https://Gist.github.com/rdp/8af84fbb54a430df8fc

s 10M řadami:

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

se 100M řadami:

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

Z toho tedy usuzuji následující: kapka je „asi“ stejně rychlá (nebo rychlejší) jako zkrácená + kapka (alespoň u moderních verzí Postgresu), pokud však plánujete také otočit a znovu vytvořit stůl, můžete jako dobře se držte s rovným zkrácením, které je rychlejší než kapka + znovu vytvořte (dává to smysl). FWIW.

poznámka 1: https://stackoverflow.com/questions/11419536/postgresql-truncation-speed/11423886#11423886 (říká, že postgres 9.2 může mít rychlejší zkrácení než předchozí verze). Jako vždy, benchmark s vlastním systémem vidět jeho vlastnosti.

poznámka 2: zkrácení lze vrátit zpět v postgresu, pokud se jedná o transakci: http://www.postgresql.org/docs/8.4/static/sql-truncate.html

poznámka 3: zkrácení může být u malých tabulek někdy pomalejší než smazání: https://stackoverflow.com/questions/11419536/postgresql-truncation-speed/11423886#11423886

2
rogerdpack

Přidání nějaké historické perspektivy ...

Vynechání tabulky vyžaduje aktualizaci několika systémových tabulek, což zase obvykle vyžaduje provedení těchto změn systémových tabulek v jedné transakci (například „start tran, smazat syscolumns, smazat sysobjects, commit“).

Do „drop tabulky“ je rovněž zahrnuta potřeba rozvrhnout všechna data/indexové stránky spojené s tabulkou.

Před mnoha, mnoha, mnoha lety ... byl proces transakce přidělení prostoru zahrnut do transakce, která také aktualizovala systémové tabulky; čistým výsledkem bylo, že čím větší je počet přidělených stránek, tím déle to trvalo přemístění zmíněných stránek, čím déle transakce (na systémových tabulkách) byla ponechána otevřená, a tak byla větší šance na blokování (na systémových tabulkách) další procesy, které se pokoušejí vytvořit/zrušit tabulky v tempdb (zejména ošklivé se staršími allpages == stránkami na úrovni stránky a potenciál pro tabulku) - eskalace úrovně zámku).

Jednou z dřívějších metod (tehdy zpět) ke snížení soupeření o systémových tabulkách bylo zkrácení doby, kdy byly zámky drženy na systémových tabulkách, a jedním (relativně) snadným způsobem, jak toho dosáhnout, bylo rozdat stránky s údaji/indexem před upuštěním stůl.

Zatímco truncate table nerozděluje všechny datové/indexové stránky, rozděluje všechny kromě jednoho 8stránkového (datového) rozsahu; dalším „hackem“ bylo před zrušením tabulky zrušit všechny indexy (ano, oddělit txn na sysindexech, ale menší txn pro přetažení tabulky).

Když se domníváte, že (opět před mnoha, mnoha lety) existovala pouze jediná databáze „tempdb“, a některé aplikace způsobily TĚŽKÉ použití této jediné ' tempdb 'databáze, všechny' hacks ', které by mohly snížit tvrzení o systémových tabulkách v' tempdb 'byly prospěšné; postupem času se věci zlepšily ... několik dočasných databází, zamykání na úrovni řádků v systémových tabulkách, lepší metody deallokace atd.

Mezitím použití truncate table nic neublíží, pokud zůstane v kódu.

1
markp-fuso