Neden hem TRUNCATE hem de DROP kullanıyorsunuz?


100

Üzerinde çalıştığım sistemde çok sayıda saklı yordam ve geçici tablolardan yararlanan SQL betikleri var. Bu tabloları kullandıktan sonra onları bırakmak iyi bir uygulamadır.

Meslektaşlarımın çoğu (neredeyse tamamı benden çok daha deneyimli) genellikle bunu yapıyor:

TRUNCATE TABLE #mytemp
DROP TABLE #mytemp

Genellikle betiğimde bir tane kullanırım DROP TABLE.

TRUNCATEHemen önce yapmak için herhangi bir iyi sebep var mı DROP?

Yanıtlar:


130

Hayır.

TRUNCATEve DROPdavranış ve hızda neredeyse aynıdır, bu nedenle a'nın TRUNCATEhemen öncesinde bir hak yapmak DROPgereksizdir.


Not: Bu cevabı bir SQL Server perspektifinden yazdım ve onun Sybase'e eşit şekilde uygulanacağını varsaydım. Bu tamamen durum böyle görünmüyor .

Not: Bu cevabı ilk gönderdiğimde, diğer bazı yüksek dereceli cevaplar vardı - o zaman kabul edilen cevap da dahil - bazı yanlış iddialarda bulunuldu: TRUNCATEgünlüğe kaydedilmedi; TRUNCATEgeri alınamaz; TRUNCATEdaha hızlı DROP; vb.

Şimdi bu konu temizlendi, ardından izleyen isyancılar orijinal soruya teğet görünebilir. Onları burada, bu mitleri küçümsemek isteyenlerin referansı olarak bırakıyorum.


Bu TRUNCATE-then-DROPmodeli motive etmiş olabilecek birkaç popüler yanlışlık var - deneyimli DBA'lar arasında bile yaygın - . Onlar:

  • Efsane : TRUNCATEgünlüğe kaydedilmez, bu nedenle geri alınamaz.
  • Efsane : TRUNCATEdaha hızlı DROP.

Bu yanlışlıkları reddetmeme izin verin. Bu yeniden kodu bir SQL Server perspektifinden yazıyorum, ancak burada söylediğim her şey Sybase için aynı derecede geçerli olmalı.

KESILMESINDEN olduğu kaydediliyor ve bu edebilir geri alınması.

  • TRUNCATEböylece bir günlüğe operasyonu olduğunu o olabilir geri alınması . Sadece bir işlem sarın.

    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;
    

    Ancak, Not şudur doğru değil Oracle için . Günlüğe kaydedilir ve Oracle'ın geri alma ile korunan ve işlevselliğini yeniden yap, olsa TRUNCATEve diğer DDL ifadeleri olamaz Oracle sorunlar nedeniyle kullanıcı tarafından geri alınması örtülü taahhüt öncesi ve bütün DDL ifadeleri hemen sonra.

  • TRUNCATEtamamen giriş yapılmasına karşın , minimum düzeyde giriş yapılır. Bu ne anlama geliyor? Sana TRUNCATEbir masa söyle . Silinen her satırı işlem günlüğüne yerleştirmek yerine, TRUNCATEyaşadıkları veri sayfalarını ayrılmamış olarak işaretler. Bu yüzden bu kadar hızlı. Bu nedenle, TRUNCATEbir günlük okuyucusu kullanarak -ed tablosunun satırlarını işlem günlüğünden kurtaramazsınız . Tüm bulacaksınız, tahsis edilmemiş veri sayfalarına referanslar.

    Bunu ile karşılaştırın DELETE. Size ise DELETEtüm satırlarda bir tabloda ve hala teoride işlem günlüğüne silinmiş satırları bulmak ve oradan kurtarabilirsiniz hareketi tamamlamak. Çünkü DELETEsilinen her satırı işlem günlüğüne yazar. Büyük masalar için bu, durumdan çok daha yavaş olacaktır TRUNCATE.

DROP TRUNCATE kadar hızlı.

  • Gibi TRUNCATE, DROPminimal olarak kaydedilmiş bir işlemdir. Bu da DROPgeri alınabilir demektir . Bu da demektir tam olarak aynı şekilde çalışır olarak TRUNCATE. Tek tek satırları silmek yerine DROP, uygun veri sayfalarını ayrılmamış olarak işaretler ve ayrıca tablonun meta verilerini silinmiş olarak işaretler .
  • Çünkü TRUNCATEve DROPaynı şekilde çalışması, onlar kadar hızlı birbirlerine hem çalıştırın. Bir tabloyu hazırlamadan önce hiçbir anlamı yoktur . TRUNCATEDROPBana inanmıyorsanız, geliştirme örneğinizde bu demo komut dosyasını çalıştırın .

    Sıcak bir önbelleğe sahip yerel makinemde elde ettiğim sonuçlar şöyle:

    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
    

    Yani, bir 134 için milyon satır tablodan hem DROPve TRUNCATEtüm etkin bir şekilde hiçbir zaman ayırın. (Soğuk cache onlar ilk çalıştırma veya iki için yaklaşık 2-3 saniye sürebilir.) Ben de daha yüksek ortalama süresi inanıyoruz TRUNCATEardından DROPoperasyon benim yerel makinede yük değişimlerine yüklenebilir ve olmayan kombinasyon sihirli bir şekilde olduğu için bireysel işlemlerden daha kötü büyüklük sırası. Sonuçta, onlar neredeyse tamamen aynı şey.

    Bu işlemlerin ek yükü ile ilgili daha fazla ayrıntıyla ilgileniyorsanız, Martin'in bu konuda basit bir açıklaması vardır.


52

Testin TRUNCATEardından doğrudan doğrudan DROPyapmak DROPyerine, ilk yaklaşımın aslında hafifçe artan bir kayıt ek yükü bulunduğunu ve bu nedenle hafif bir karşı üretken bile olabileceğini gösteriyor.

Bireysel kayıt kayıtlarına bakmak TRUNCATE ... DROP, DROPsürümün bu ek girişlerin dışında sürümle neredeyse aynı olduğunu gösteriyor .

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

Böylece TRUNCATEilk sürüm aşağıdaki gibi çeşitli sistem tablolarında bazı güncellemeler yapmak için biraz çaba harcayarak bitiyor

  • İçindeki rcmodifiedtüm tablo sütunları için güncellemesys.sysrscols
  • Güncelleme rcrowsiçindesysrowsets
  • Sıfır dışarı pgfirst, pgroot, pgfirstiam, pcused, pcdata, pcreservediçindesys.sysallocunits

Bu sistem tablo satırları, yalnızca tablo bir sonraki ifadeye bırakıldığında silinir.

TRUNCATEVs tarafından gerçekleştirilen kütüğün tamamen parçalanması DROPaşağıdadır. DELETEKarşılaştırma amacıyla da ekledim .

+-------------------+-------------------+--------------------+------------------+-----------+---------------+-------------+------------------+-----------+---------------+-------------+
|                   |                   |                    |                            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, her sayfada bir satır olmak üzere 1000 satırlık bir masaya karşı tam kurtarma modeline sahip bir veri tabanında gerçekleştirildi. Kök dizin sayfası ve 3 orta seviye dizin sayfası nedeniyle tablo toplamda 1.004 sayfa tüketmektedir.

Bu sayfaların 8 tanesi, kalanı 125 Uniform Extents'a dağıtılmış karışık uzantılarda tek sayfa tahsisleridir. 8 tek sayfa ayırma işlemi 8 LOP_MODIFY_ROW,LCX_IAMgünlük girişi olarak gösterilir. 125 dereceye kadar ayrılma olarak LOP_SET_BITS LCX_GAM,LCX_IAM. Bu işlemlerin her ikisi de PFS, birleştirilmiş 133 LOP_MODIFY_ROW, LCX_PFSgirişler dolayısıyla ilişkili sayfada bir güncelleme gerektirir . Sonra tablo gerçekte bırakıldığında, bunun hakkındaki meta verilerin çeşitli sistem tablolarından kaldırılması gerekir, bu nedenle 22 sistem tablosu LOP_DELETE_ROWSgünlük girdisi (aşağıdaki gibi hesaplanır)

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

Tam Komut Dosyası Altında

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

2

Tamam, herhangi bir "sıcak önbellekleme" ye dayanmayan bazı testler yapmaya çalışacağımı düşündüm, böylece daha gerçekçi bir test olacaklardı (Postgres'i kullanarak, gönderilen diğer cevapların aynı özelliklerine uyup uymadığını görmek için) :

Kıyaslama kriterlerim 9.3.4'ü büyük-ish veritabanıyla kullanıyor (umarım RAM önbelleğine sığmayacak kadar büyük):

Bu test DB komut dosyasını kullanarak: https://gist.github.com/rdp/8af84fbb54a430df8fc0

10M satır ile:

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

100M satır ile:

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

Bu nedenle, şunu kabul ediyorum: bırakma, kesik + bırakma (en azından Postgres'in modern versiyonları için) kadar hızlı (ya da daha hızlı) kadar "yaklaşık" dır, ancak, masanın etrafında dönüp yeniden yaratmayı planlıyorsanız, iyi bir damla + rekreasyon daha hızlı (mantıklı) daha hızlı olan, düz bir kesme yapmak ile sopa. FWIW.

Not 1: https://stackoverflow.com/questions/11419536/postgresql-truncation-speed/11423886#11423886 (postgres 9.2'nin önceki sürümlerden daha hızlı bir kesime sahip olabileceğini söylüyor). Her zaman olduğu gibi, özelliklerini görmek için kendi sisteminizle kıyaslayın.

Not 2: İşlem bittiğinde, kesme işlemi postgres'te geri alınabilir: http://www.postgresql.org/docs/8.4/static/sql-truncate.html

not 3: kesme, küçük tablolarla bazen silme işleminden daha yavaş olabilir: https://stackoverflow.com/questions/11419536/postgresql-truncation-speed/11423886#11423886


1

Tarihsel bir bakış açısı eklemek ...

Bir tabloyu düşürmek birkaç sistem tablosunun güncellenmesini gerektirir; bu da genellikle bu sistem tablosu değişikliklerinin tek bir işlemde yapılmasını gerektirir ("tran başla, sistem sütunlarını sil, sysobjects sil, onayla").

Ayrıca 'bırakma tablosuna' dahil edilmiş olması, tabloyla ilişkili tüm veri / indeks sayfalarının ayrılması gereğidir.

Çok, çok, çok, yıllar önce ... uzay tahsis süreci, sistem tablolarını da güncelleyen işleme dahil edildi, net sonuç, tahsis edilen sayfaların sayısı arttıkça, söz konusu sayfaların tahsisi ne kadar uzun sürerse işlem (sistem tablolarında) açık bırakıldı ve böylece tempdb'de tablo oluşturmaya / bırakmaya çalışan diğer işlemleri (sistem tablolarında) engelleme şansı (özellikle eski allpages ile kötü = = sayfa düzeyinde kilitleme ve tablo potansiyeli) düzey kilidi yükselmesi).

Sistem tablolarındaki çekişmeyi azaltmak için kullanılan eski yöntemlerden biri (o zamana kadar) sistem masalarında kilitlerin tutulma süresini azaltmaktı ve bunu yapmanın bir (nispeten) kolay yolu, veri / dizin sayfalarını bırakmadan önce dağıtmaktı. Masa

İken truncate tableayırması değil tüm veriler / index sayfaları, bu bir 8 sayfa (veri) dereceye ancak tüm ayırması yapar; bir başka 'hack' daha sonra masayı düşürmeden önce tüm indeksleri düşürmekti (evet, sysindex'lerde ayrı txn ama bırakma tablosu için daha küçük bir txn).

Bunu düşündüğünüz zaman (yine, çok, yıllar önce) sadece tek bir 'tempdb' veritabanı vardı ve bazı uygulamalar , sistem tablolarındaki çekişmeyi azaltabilecek herhangi bir 'hack'i (O) tek bir' tempdb 'veritabanını AĞIR kullandı. 'tempdb' yararı vardı; zamanla işler düzeldi ... çoklu geçici veritabanları, sistem masalarında satır düzeyinde kilitleme, daha iyi tahliye yöntemleri, vb.

Bu arada, kullanımı truncate table, kodda bırakıldığı takdirde hiçbir şeye zarar vermez.


-2

Yabancı anahtarlara sahip tablolar için TRUNCATE yapmak mantıklıdır. Ancak, geçici tablolar için sadece DROP yeterlidir


TRUNCATE bir şekilde yabancı anahtar çatışmalarından kaçınır mı? Nasıl?
user259412

1
Yabancı anahtar olduğuna dair bir hata yazacak
Evgeniy Gribkov

-8

Bunun truncateamacı, tablodaki her şeyi basitçe ve geri dönülmez bir şekilde kaldırmaktır (veri deposu motorlarına dayanan bazı teknik özellikler biraz değişebilir) - ağır kütük kayıtlarını atlamak, vb.

drop tableDeğişiklikler yapıldığı sırada tüm değişiklikleri günlüğe kaydeder. Bu yüzden, en az miktarda tomruk tutmak ve işe yaramaz sistem kaybını azaltmak için, önce çok büyük bir masanın kesilebileceğinden, sonra bırakılabileceğinden şüpheleniyorum.

truncate Elbette, işlemi geri almanıza izin verecek bir işlem (en güvenli seçenek olacaktır) ile tamamlanabilir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.