it-swarm.asia

Mengapa menggunakan TRUNCATE dan DROP?

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 rekan saya (hampir semuanya jauh lebih berpengalaman daripada saya) biasanya melakukan ini:

TRUNCATE TABLE #mytemp
DROP TABLE #mytemp

Saya biasanya menggunakan satu DROP TABLE dalam skrip saya.

Apakah ada alasan bagus untuk melakukan TRUNCATE segera sebelum DROP?

102
user606723

Tidak.

TRUNCATE dan DROP hampir identik dalam perilaku dan kecepatan, sehingga melakukan TRUNCATE tepat sebelum DROP hanya 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 salah seperti: TRUNCATE tidak dicatat; TRUNCATE tidak dapat diputar kembali; TRUNCATE lebih cepat dari DROP; dll.

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


Ada beberapa kepalsuan yang populer - meresap bahkan di antara para DBA yang berpengalaman - yang mungkin telah memotivasi ini TRUNCATE-then-DROP pola. Mereka:

  • Mitos : TRUNCATE tidak dicatat, oleh karena itu tidak dapat dibatalkan.
  • Mitos : TRUNCATE lebih 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.

MEMOTONG dicatat , dan itu dapat digulirkan kembali.

  • TRUNCATE adalah operasi yang dicatat, jadi itu dapat digulung kembali . Hanya membungkusnya dalam 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, TRUNCATE dan pernyataan DDL lainnya tidak dapat dibatalkan oleh pengguna karena Oracle mengeluarkan implisit commit segera sebelum dan sesudah semua pernyataan DDL.

  • TRUNCATE minimal dicatat , tidak seperti yang dicatat sepenuhnya. Apa artinya? Katakan Anda TRUNCATE sebuah tabel. Alih-alih menempatkan setiap baris yang dihapus dalam log transaksi, TRUNCATE hanya menandai halaman data tempat mereka hidup sebagai tidak terisi. Itu sebabnya sangat cepat. Itu juga mengapa Anda tidak dapat memulihkan baris tabel TRUNCATE- 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 DELETE semua baris dalam sebuah tabel dan melakukan transaksi Anda masih bisa, secara teori, menemukan baris yang dihapus dalam log transaksi dan memulihkannya dari sana. Itu karena DELETE menulis setiap baris yang dihapus ke log transaksi. Untuk tabel besar, ini akan membuatnya jauh lebih lambat daripada TRUNCATE.

DROP sama cepatnya dengan TRUNCATE.

  • Seperti TRUNCATE, DROP adalah operasi yang dicatat minimal. Itu berarti DROP dapat diputar kembali terlalu. Itu juga berarti berfungsi dengan cara yang persis sama sebagai TRUNCATE. Alih-alih menghapus baris individual, DROP menandai halaman data yang sesuai sebagai tidak terisi dan juga menandai metadata tabel sebagai dihapus .
  • Karena TRUNCATE dan DROP bekerja dengan cara yang persis sama, mereka berjalan secepat satu sama lain. Tidak ada gunanya TRUNCATE- ing tabel sebelum DROP- ing itu. Jalankan ini script demo pada contoh pengembangan Anda jika Anda tidak percaya kepada saya.

    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 tabel tabel 134 juta baik DROP dan TRUNCATE tidak membutuhkan 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 TRUNCATE lalu DROP disebabkan karena memuat variasi pada mesin lokal saya dan bukan karena kombinasi tersebut entah bagaimana secara ajaib urutan besarnya lebih buruk daripada operasi individu. Bagaimanapun, mereka hampir persis sama.

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

132
Nick Chammas

Pengujian TRUNCATE lalu DROP vs hanya melakukan DROP secara langsung menunjukkan bahwa pendekatan pertama sebenarnya memiliki sedikit peningkatan overhead logging sehingga bahkan mungkin sedikit kontraproduktif.

Melihat masing-masing catatan log menunjukkan versi TRUNCATE ... DROP Hampir identik dengan versi DROP 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 versi pertama TRUNCATE akhirnya menghabiskan sedikit usaha melakukan beberapa pembaruan ke berbagai tabel sistem sebagai berikut

  • Perbarui rcmodified untuk semua kolom tabel dalam sys.sysrscols
  • Perbarui rcrows di sysrowsets
  • Hapus nol pgfirst, pgroot, pgfirstiam, pcused, pcdata, pcreserved dalam sys.sysallocunits

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

Perincian penuh logging yang dilakukan oleh TRUNCATE vs DROP ada di bawah ini. Saya juga menambahkan DELETE dalam untuk 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        |
+-------------------+-------------------+--------------------+------------------+-----------+---------------+-------------+------------------+-----------+---------------+-------------+

Tes 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 ini adalah alokasi halaman tunggal dalam luasan campuran dengan sisanya didistribusikan di 125 Luas Seragam. Delapan alokasi satu halaman 8 muncul sebagai entri log 8 LOP_MODIFY_ROW,LCX_IAM. 125 perluasan deallocations sebagai LOP_SET_BITS LCX_GAM,LCX_IAM. Kedua operasi ini juga memerlukan pembaruan untuk halaman PFS yang terkait karenanya entri 133 LOP_MODIFY_ROW, LCX_PFS Yang digabungkan. Kemudian ketika tabel sebenarnya menjatuhkan metadata tentang itu perlu dihapus dari berbagai tabel sistem maka entri tabel sistem LOP_DELETE_ROWS Tabel 22 (dicatat seperti di bawah ini)

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

Script Lengkap Di Bawah

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 saya pikir saya akan mencoba untuk melakukan beberapa tolok ukur yang tidak bergantung pada "cache hangat" sehingga mudah-mudahan itu akan menjadi tes yang lebih realistis (juga menggunakan Postgres, untuk melihat apakah cocok dengan karakteristik yang sama dari jawaban yang diposting lainnya) :

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

Menggunakan skrip DB tes ini: https://Gist.github.com/rdp/8af84fbb54a430df8fc

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.

note 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.

catatan 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

2
rogerdpack

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 halaman data/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.

Sementara truncate table tidak membatalkan alokasi semua data/halaman indeks, ia membatalkan alokasi 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 dibuat HEAVY penggunaan single itu ' tempdb 'database,' hacks '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 table tidak ada ruginya jika dibiarkan dalam kode.

1
markp-fuso