Mengapa menggunakan TRUNCATE dan DROP?

100

Dalam sistem yang saya kerjakan ada banyak prosedur tersimpan dan skrip SQL yang menggunakan tabel sementara. Setelah menggunakan tabel-tabel ini adalah praktik yang baik untuk menjatuhkannya.

Banyak kolega saya (hampir semuanya jauh lebih berpengalaman daripada saya) biasanya melakukan ini:

TRUNCATE TABLE #mytemp
DROP TABLE #mytemp

Saya biasanya menggunakan satu DROP TABLEdi skrip saya.

Apakah ada alasan bagus untuk TRUNCATEsegera melakukan sebelum DROP?

pengguna606723
sumber

Jawaban:

130

Tidak.

TRUNCATEdan DROPhampir identik dalam perilaku dan kecepatan, jadi melakukan yang TRUNCATEbenar sebelum benar- DROPbenar tidak perlu.


Catatan: Saya menulis jawaban ini dari perspektif SQL Server dan menganggap itu akan berlaku sama untuk Sybase. Tampaknya ini tidak sepenuhnya terjadi .

Catatan: Ketika saya pertama kali memposting jawaban ini, ada beberapa jawaban berperingkat tinggi lainnya - termasuk jawaban yang diterima saat itu - yang membuat beberapa klaim palsu seperti: TRUNCATEtidak dicatat; TRUNCATEtidak bisa digulung kembali; TRUNCATElebih cepat dari DROP; dll.

Sekarang setelah utas ini telah dibersihkan, bantahan yang mengikutinya mungkin tampak bersinggungan dengan pertanyaan awal. Saya meninggalkan mereka di sini sebagai referensi bagi orang lain yang ingin menyanggah mitos-mitos ini.


Ada beberapa kepalsuan yang populer - merasuk bahkan di antara para DBA yang berpengalaman - yang mungkin memotivasi TRUNCATE-then-DROPpola ini . Mereka:

  • Mitos : TRUNCATEtidak dicatat, oleh karena itu tidak dapat diputar kembali.
  • Mitos : TRUNCATElebih cepat dari DROP.

Biarkan saya membantah kepalsuan ini. Saya menulis bantahan ini dari perspektif SQL Server, tetapi semua yang saya katakan di sini harus sama-sama berlaku untuk Sybase.

Truncate adalah login, dan dapat diperpanjang kembali.

  • TRUNCATEadalah operasi login, sehingga hal itu dapat digulung kembali . Hanya membungkusnya dalam suatu transaksi.

    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;

    Perhatikan, bagaimanapun, bahwa ini tidak benar untuk Oracle . Meskipun dicatat dan dilindungi oleh fungsionalitas pembatalan dan pengulangan Oracle, TRUNCATEdan pernyataan DDL lainnya tidak dapat dibatalkan oleh pengguna karena masalah Oracle secara implisit langsung dilakukan sebelum dan sesudah semua pernyataan DDL.

  • TRUNCATElog minimal , dibandingkan dengan log sepenuhnya. Apa artinya? Katakan kamu TRUNCATEmeja. Alih-alih menempatkan setiap baris yang dihapus dalam log transaksi, TRUNCATEcukup tandai halaman data tempat mereka hidup sebagai tidak terisi. Itu sebabnya sangat cepat. Itu juga mengapa Anda tidak dapat memulihkan baris TRUNCATEtabel -ed dari log transaksi menggunakan pembaca log. Semua Anda akan menemukan ada referensi ke halaman data yang tidak terisi.

    Bandingkan ini dengan DELETE. Jika Anda DELETEsemua baris dalam tabel dan melakukan transaksi, Anda masih dapat, secara teori, menemukan baris yang dihapus dalam log transaksi dan memulihkannya dari sana. Itu karena DELETEmenulis setiap baris yang dihapus ke log transaksi. Untuk tabel besar, ini akan membuatnya jauh lebih lambat daripada TRUNCATE.

DROP secepat TRUNCATE.

  • Seperti TRUNCATE, DROPadalah operasi yang dicatat minimal. Itu berarti DROPdapat diputar kembali juga. Itu juga berarti bekerja dengan cara yang sama seperti TRUNCATE. Alih-alih menghapus baris individual, DROPtandai halaman data yang sesuai sebagai tidak terisi dan tandai metadata tabel sebagai dihapus .
  • Karena TRUNCATEdan DROPbekerja dengan cara yang persis sama, mereka berlari secepat satu sama lain. Tidak ada gunanya untuk TRUNCATEmembuat tabel sebelum melakukan DROPitu. Jalankan skrip demo ini pada contoh pengembangan Anda jika Anda tidak percaya padaku.

    Di mesin lokal saya dengan cache hangat, hasil yang saya dapatkan adalah sebagai berikut:

    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

    Jadi, untuk 134 juta baris tabel baik DROPdan tidak TRUNCATEmemakan waktu sama sekali. (Pada cache yang dingin mereka membutuhkan sekitar 2-3 detik untuk menjalankan pertama atau dua.) Saya juga percaya bahwa durasi rata-rata yang lebih tinggi untuk operasi TRUNCATEkemudian DROPdisebabkan oleh memuat variasi pada mesin lokal saya dan bukan karena kombinasi itu entah bagaimana ajaib dan urutan besarnya lebih buruk daripada operasi individu. Bagaimanapun, mereka hampir persis sama.

    Jika Anda tertarik lebih detail tentang overhead logging dari operasi ini, Martin memiliki penjelasan langsung tentang itu.

Nick Chammas
sumber
52

Pengujian TRUNCATEkemudian DROPvs hanya melakukan DROPsecara langsung menunjukkan bahwa pendekatan pertama sebenarnya memiliki sedikit peningkatan biaya penebangan sehingga bahkan mungkin sedikit kontraproduktif.

Melihat catatan log individual menunjukkan TRUNCATE ... DROPversi ini hampir identik dengan DROPversi kecuali ada entri tambahan ini.

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

Jadi TRUNCATEversi pertama berakhir dengan sedikit usaha melakukan beberapa pembaruan ke berbagai tabel sistem sebagai berikut

  • Perbarui rcmodifieduntuk semua kolom tabel disys.sysrscols
  • Perbarui rcrowsdalamsysrowsets
  • Nol pgfirst, pgroot, pgfirstiam, pcused, pcdata, pcreserveddisys.sysallocunits

Baris-baris tabel sistem ini hanya berakhir dengan dihapus ketika tabel dijatuhkan dalam pernyataan berikutnya.

Perincian penuh logging yang dilakukan oleh TRUNCATEvs DROPada di bawah ini. Saya juga menambahkan DELETEuntuk tujuan perbandingan.

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

Pengujian dilakukan dalam database dengan model pemulihan penuh terhadap tabel 1.000 baris dengan satu baris per halaman. Tabel ini mengkonsumsi total 1.004 halaman karena halaman indeks akar dan 3 halaman indeks tingkat menengah.

8 dari halaman-halaman ini adalah alokasi halaman tunggal dalam luasan campuran dengan sisanya didistribusikan di 125 Luas Seragam. Delapan 8 halaman de-alokasi muncul sebagai 8 LOP_MODIFY_ROW,LCX_IAMentri log. Deallocations sejauh 125 sebagai LOP_SET_BITS LCX_GAM,LCX_IAM. Kedua operasi ini juga memerlukan pembaruan ke PFShalaman terkait sehingga 133 LOP_MODIFY_ROW, LCX_PFSentri gabungan . Kemudian ketika tabel sebenarnya menjatuhkan metadata tentang itu perlu dihapus dari berbagai tabel sistem maka LOP_DELETE_ROWSentri log tabel sistem 22 (diperhitungkan sebagai berikut)

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

Tulisan Lengkap Di Bawah Ini

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
Martin Smith
sumber
2

OK saya pikir saya akan mencoba melakukan beberapa tolok ukur yang tidak bergantung pada "warm cacheing" sehingga mudah-mudahan itu akan menjadi tes yang lebih realistis (juga menggunakan Postgres, untuk melihat apakah itu cocok dengan karakteristik yang sama dari jawaban yang diposting lainnya) :

Benchmark saya menggunakan postgres 9.3.4 dengan database besar-ish, (semoga cukup besar untuk tidak muat dalam cache RAM):

Menggunakan skrip DB tes ini: https://gist.github.com/rdp/8af84fbb54a430df8fc0

dengan baris 10 jt:

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

dengan 100M baris:

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

Jadi dari sini saya menduga yang berikut: drop adalah "tentang" secepat (atau lebih cepat) sebagai truncate + drop (setidaknya untuk versi modern Postgres), namun, jika Anda berencana juga membalikkan dan menciptakan kembali tabel, Anda dapat baik tetap dengan melakukan pemotongan lurus, yang lebih cepat daripada setetes + menciptakan kembali (masuk akal). FWIW.

Catatan 1: https://stackoverflow.com/questions/11419536/postgresql-truncation-speed/11423886#11423886 (mengatakan bahwa postgres 9.2 mungkin memiliki truncate yang lebih cepat daripada versi sebelumnya). Seperti biasa, patok dengan sistem Anda sendiri untuk melihat karakteristiknya.

note 2: truncate dapat diputar kembali di postgres, jika dalam transaksi: http://www.postgresql.org/docs/8.4/static/sql-truncate.html

note 3: truncate can, dengan tabel kecil, kadang-kadang lebih lambat daripada delete: https://stackoverflow.com/questions/11419536/postgresql-truncation-speed/11423886#11423886

rogerdpack
sumber
1

Menambahkan beberapa perspektif historis ...

Menjatuhkan tabel membutuhkan memperbarui beberapa tabel sistem, yang pada gilirannya biasanya mengharuskan membuat tabel sistem ini berubah dalam satu transaksi (pikirkan "mulai tran, hapus syscolumns, hapus sysobjects, komit").

Juga termasuk dalam 'tabel drop' adalah kebutuhan untuk membatalkan alokasi semua data / halaman indeks yang terkait dengan tabel.

Banyak, bertahun-tahun yang lalu ... proses alokasi ruang dimasukkan dalam transaksi yang juga memperbarui tabel sistem; hasil bersihnya adalah semakin besar jumlah halaman yang dialokasikan, semakin lama waktu yang dibutuhkan untuk membatalkan alokasi halaman tersebut, semakin lama transaksi (pada tabel sistem) dibiarkan terbuka, dan dengan demikian peluang lebih besar untuk memblokir (pada tabel sistem) proses lain yang mencoba membuat / menjatuhkan tabel di tempdb (terutama tidak menyenangkan dengan halaman yang lebih lama == penguncian tingkat halaman dan potensi untuk tabel eskalasi kunci tingkat).

Salah satu metode awal yang digunakan (saat itu) untuk mengurangi pertikaian pada tabel sistem adalah dengan mengurangi waktu kunci dipegang pada tabel sistem, dan satu (relatif) cara mudah untuk melakukan ini adalah dengan membatalkan alokasi data / halaman indeks sebelum menjatuhkan meja.

Meskipun truncate tabletidak membatalkan alokasi semua data / halaman indeks, itu tidak mengalokasikan semua kecuali satu tingkat (data) 8 halaman; 'hack' lain adalah untuk kemudian menjatuhkan semua indeks sebelum menjatuhkan tabel (yeah, pisahkan txn pada sysindexes tetapi txn yang lebih kecil untuk drop table).

Ketika Anda mempertimbangkan itu (sekali lagi, bertahun-tahun yang lalu) hanya ada satu database 'tempdb', dan beberapa aplikasi membuat HEAVY menggunakan database 'tempdb' tunggal itu, 'peretasan' apa pun yang dapat mengurangi pertikaian pada tabel sistem di 'tempdb' bermanfaat; seiring waktu hal-hal telah membaik ... beberapa basis data sementara, penguncian tingkat baris pada tabel sistem, metode deallokasi yang lebih baik, dll.

Sementara itu penggunaan truncate tabletidak ada salahnya jika dibiarkan dalam kode.

markp
sumber
-2

Masuk akal untuk melakukan TRUNCATE untuk tabel yang memiliki kunci asing. Namun, untuk tabel sementara cukup DROP sudah cukup

Evgeniy Gribkov
sumber
TRUNCATE akan menghindari konflik kunci asing? Bagaimana?
user259412
1
Akan menulis kesalahan bahwa ada kunci asing
Evgeniy Gribkov
-8

Maksudnya truncateadalah untuk menghapus segala sesuatu di tabel secara sederhana dan tidak dapat dibatalkan (beberapa spesifikasi teknis berdasarkan mesin penyimpanan data mungkin sedikit berbeda) - melewatkan penebangan yang berat, dll.

drop tablemencatat semua perubahan saat perubahan sedang dilakukan. Jadi, untuk memiliki minimal logging dan mengurangi churn sistem yang tidak berguna, saya akan curiga meja yang sangat besar dapat dipotong terlebih dahulu, kemudian dijatuhkan.

truncate dapat dibungkus dalam suatu transaksi (yang akan menjadi pilihan paling aman) yang, tentu saja, akan memungkinkan Anda untuk memutar kembali operasi.

Ekstrak
sumber