Tại sao nên sử dụng cả TRUNCATE và DROP?


100

Trong hệ thống tôi làm việc có rất nhiều thủ tục được lưu trữ và các tập lệnh SQL sử dụng các bảng tạm thời. Sau khi sử dụng các bảng này, thực hành tốt để thả chúng.

Nhiều đồng nghiệp của tôi (hầu hết tất cả những người có nhiều kinh nghiệm hơn tôi) thường làm điều này:

TRUNCATE TABLE #mytemp
DROP TABLE #mytemp

Tôi thường sử dụng một DROP TABLEtrong các kịch bản của tôi.

Có bất kỳ lý do tốt để làm TRUNCATEngay lập tức trước khi a DROP?

Câu trả lời:


130

Không.

TRUNCATEDROPgần như giống hệt nhau về hành vi và tốc độ, do đó, việc làm TRUNCATEđúng trước khi DROPđơn giản là không cần thiết.


Lưu ý: Tôi đã viết câu trả lời này từ phối cảnh Máy chủ SQL và cho rằng nó sẽ áp dụng như nhau cho Sybase. Có vẻ như đây không phải là hoàn toàn .

Lưu ý: Khi tôi đăng câu trả lời này lần đầu tiên, có một số câu trả lời được đánh giá cao khác - bao gồm câu trả lời được chấp nhận sau đó - đã đưa ra một số tuyên bố sai như: TRUNCATEkhông được ghi lại; TRUNCATEkhông thể cuộn lại; TRUNCATEnhanh hơn DROP; Vân vân.

Bây giờ chủ đề này đã được làm sạch, các phản bác tiếp theo có thể tiếp tuyến với câu hỏi ban đầu. Tôi để chúng ở đây như một tài liệu tham khảo cho những người khác đang tìm cách gỡ rối những huyền thoại này.


Có một vài sai lầm phổ biến - phổ biến ngay cả trong số các DBA có kinh nghiệm - có thể đã thúc đẩy TRUNCATE-then-DROPmô hình này . Họ đang:

  • Quan niệm : TRUNCATEkhông được ghi lại, do đó nó không thể được khôi phục.
  • Quan niệm : TRUNCATElà nhanh hơn DROP.

Hãy để tôi bác bỏ những sự giả dối. Tôi đang viết phản bác này từ góc độ Máy chủ SQL, nhưng mọi thứ tôi nói ở đây nên được áp dụng như nhau cho Sybase.

TRUNCATE được ghi lại, và nó có thể được khôi phục.

  • TRUNCATElà một hoạt động đăng nhập, vì vậy có thể được khôi phục . Chỉ cần bọc nó trong một giao dịch.

    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;
    

    Lưu ý, tuy nhiên, điều này không đúng với Oracle . Mặc dù được ghi lại và bảo vệ bởi chức năng hoàn tác và làm lại của Oracle, TRUNCATEvà các câu lệnh DDL khác không thể được người dùng khôi phục vì các vấn đề của Oracle cam kết ngay lập tức trước và sau tất cả các câu lệnh DDL.

  • TRUNCATEđược ghi lại tối thiểu , trái ngược với đăng nhập đầy đủ. Điều đó nghĩa là gì? Nói với bạn TRUNCATEmột cái bàn. Thay vì đặt từng hàng đã xóa trong nhật ký giao dịch, TRUNCATEchỉ cần đánh dấu các trang dữ liệu mà chúng sống là chưa được phân bổ. Đó là lý do tại sao nó quá nhanh. Đó cũng là lý do tại sao bạn không thể khôi phục các hàng của TRUNCATEbảng aed từ nhật ký giao dịch bằng trình đọc nhật ký. Tất cả bạn sẽ tìm thấy có các tài liệu tham khảo đến các trang dữ liệu được sắp xếp lại.

    So sánh điều này với DELETE. Nếu bạn DELETEtất cả các hàng trong một bảng và thực hiện giao dịch, bạn vẫn có thể tìm thấy các hàng đã xóa trong nhật ký giao dịch và khôi phục chúng từ đó. Đó là bởi vì DELETEghi tất cả các hàng đã xóa vào nhật ký giao dịch. Đối với các bảng lớn, điều này sẽ làm cho nó chậm hơn nhiều TRUNCATE.

DROP cũng nhanh như TRUNCATE.

  • Giống như TRUNCATE, DROPlà một hoạt động đăng nhập tối thiểu. Điều đó có nghĩa là DROPcó thể được cuộn lại quá. Đó cũng có nghĩa là nó hoạt động chính xác theo cùng một cách như TRUNCATE. Thay vì xóa từng hàng riêng lẻ, hãy DROPđánh dấu các trang dữ liệu phù hợp là chưa được phân bổ và đánh dấu thêm siêu dữ liệu của bảng là đã xóa .
  • Bởi vì TRUNCATEDROPhoạt động chính xác theo cùng một cách, chúng chạy nhanh như nhau. Không có điểm nào để TRUNCATE-ing một bảng trước khi DROP-ing nó. Chạy tập lệnh demo này trong trường hợp phát triển của bạn nếu bạn không tin tôi.

    Trên máy cục bộ của tôi với bộ đệm ấm, kết quả tôi nhận được như sau:

    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
    

    Vì vậy, đối với một bảng hàng 134 triệu cả hai DROPvà hoàn toàn không TRUNCATEmất thời gian. (Trên một bộ nhớ cache lạnh họ mất khoảng 2-3 giây cho chạy đầu tiên hoặc hai.) Tôi cũng tin rằng thời gian trung bình cao hơn cho TRUNCATEsau đó DROPhoạt động là do nạp biến trên máy tính địa phương của tôi và không vì sự kết hợp là bằng cách nào đó kỳ diệu một thứ tự cường độ tồi tệ hơn các hoạt động cá nhân. Rốt cuộc, họ gần như giống hệt nhau.

    Nếu bạn quan tâm đến chi tiết hơn về chi phí đăng nhập của các hoạt động này, Martin có một lời giải thích đơn giản về điều đó.


52

Thử nghiệm TRUNCATEsau đó DROPso với chỉ thực hiện DROPtrực tiếp cho thấy cách tiếp cận đầu tiên thực sự có chi phí đăng nhập tăng nhẹ nên thậm chí có thể phản tác dụng nhẹ.

Nhìn vào các bản ghi nhật ký riêng lẻ cho thấy TRUNCATE ... DROPphiên bản gần giống với DROPphiên bản ngoại trừ có các mục bổ sung này.

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

Vì vậy, TRUNCATEphiên bản đầu tiên kết thúc lãng phí một chút nỗ lực thực hiện một số cập nhật cho các bảng hệ thống khác nhau như sau

  • Cập nhật rcmodifiedcho tất cả các cột trong bảngsys.sysrscols
  • Cập nhật rcrowstrongsysrowsets
  • Không ra pgfirst, pgroot, pgfirstiam, pcused, pcdata, pcreservedtrongsys.sysallocunits

Các hàng trong bảng hệ thống này cuối cùng chỉ bị xóa khi bảng bị hủy trong câu lệnh tiếp theo.

Một phân tích đầy đủ của việc đăng nhập được thực hiện bởi TRUNCATEvs DROPbên dưới. Tôi cũng đã thêm DELETEvào để so sánh.

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

Thử nghiệm được thực hiện trong cơ sở dữ liệu với mô hình khôi phục hoàn toàn so với bảng 1.000 hàng với một hàng trên mỗi trang. Bảng tiêu thụ tổng cộng 1.004 trang do trang chỉ mục gốc và 3 trang chỉ mục cấp trung gian.

8 trong số các trang này là phân bổ một trang trong các phạm vi hỗn hợp với phần còn lại được phân bổ trên 125 Mức độ thống nhất. 8 phân bổ trang đơn hiển thị dưới dạng 8 LOP_MODIFY_ROW,LCX_IAMmục nhật ký. Mức độ thỏa thuận 125 như LOP_SET_BITS LCX_GAM,LCX_IAM. Cả hai thao tác này cũng yêu cầu cập nhật PFStrang liên quan do đó kết hợp 133 LOP_MODIFY_ROW, LCX_PFSmục. Sau đó, khi bảng thực sự bị loại bỏ siêu dữ liệu về nó cần phải được loại bỏ khỏi các bảng hệ thống khác nhau do đó 22 LOP_DELETE_ROWSmục nhật ký bảng hệ thống (chiếm như dưới đây)

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

Full Script bên dưới

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

OK nghĩ rằng tôi đã cố gắng thực hiện một số điểm chuẩn không dựa vào bất kỳ "bộ đệm ấm" nào để hy vọng chúng sẽ là một thử nghiệm thực tế hơn (cũng sử dụng Postgres, để xem liệu nó có khớp với các đặc điểm tương tự của các câu trả lời được đăng khác không) :

Điểm chuẩn của tôi bằng cách sử dụng postgres 9.3.4 với cơ sở dữ liệu lớn, (hy vọng đủ lớn để không vừa với bộ nhớ cache RAM):

Sử dụng tập lệnh DB thử nghiệm này: https://gist.github.com/rdp/8af84fbb54a430df8fc0

với 10 triệu hàng:

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

với 100M hàng:

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

Vì vậy, từ điều này tôi phỏng đoán như sau: drop là "about" nhanh (hoặc nhanh hơn) như cắt ngắn + drop (ít nhất là đối với các phiên bản hiện đại của Postgres), tuy nhiên, nếu bạn dự định cũng quay lại và tạo lại bảng, bạn có thể cũng gắn bó với việc thực hiện cắt ngắn, nhanh hơn so với thả + tái tạo (có ý nghĩa). FWIW.

lưu ý 1: https://stackoverflow.com/questions/11419536/postgresql-truncation-speed/11423886#11423886 (nói rằng postgres 9.2 có thể rút ngắn nhanh hơn các phiên bản trước). Như mọi khi, điểm chuẩn với hệ thống của riêng bạn để xem các đặc điểm của nó.

lưu ý 2: cắt ngắn có thể được khôi phục trong postgres, nếu trong một giao dịch: http://www.postgresql.org/docs/8.4/static/sql-truncate.html

lưu ý 3: cắt ngắn có thể, với các bảng nhỏ, đôi khi chậm hơn xóa: https://stackoverflow.com/questions/11419536/postgresql-truncation-speed/11423886#11423886


1

Thêm một số quan điểm lịch sử ...

Việc bỏ một bảng yêu cầu cập nhật một số bảng hệ thống, do đó thường yêu cầu thực hiện các thay đổi bảng hệ thống này trong một giao dịch (nghĩ "bắt đầu tran, xóa syscolumns, xóa sysobjects, commit").

Cũng bao gồm trong 'bảng thả' là nhu cầu phân bổ tất cả các trang dữ liệu / chỉ mục được liên kết với bảng.

Nhiều, rất nhiều năm trước ... quy trình phân bổ không gian được bao gồm trong giao dịch cũng cập nhật các bảng hệ thống, kết quả cuối cùng là số trang được phân bổ càng lớn, thời gian phân bổ các trang nói càng lâu, thời gian càng dài giao dịch (trên các bảng hệ thống) bị bỏ ngỏ và do đó, khả năng chặn (trên các bảng hệ thống) sẽ cao hơn các quy trình khác đang cố gắng tạo / thả các bảng trong tempdb (đặc biệt khó chịu với các trang cũ hơn == khóa cấp độ trang và tiềm năng cho bảng leo thang khóa -level).

Một phương pháp ban đầu được sử dụng (cách trước đó) để giảm sự tranh chấp trên các bảng hệ thống là giảm thời gian khóa được giữ trên các bảng hệ thống và một cách (tương đối) dễ dàng để làm điều này là phân bổ các trang dữ liệu / chỉ mục trước khi thả cái bàn.

Mặc dù truncate tablekhông phân bổ tất cả các trang dữ liệu / chỉ mục, nhưng nó không phân bổ tất cả trừ một phạm vi 8 trang (dữ liệu); một 'hack' khác là sau đó loại bỏ tất cả các chỉ mục trước khi thả bảng (vâng, tách txn trên sysindexes nhưng một txn nhỏ hơn cho bảng thả).

Khi bạn xem xét rằng (một lần nữa, nhiều năm trước) chỉ có cơ sở dữ liệu 'tempdb' và một số ứng dụng đã sử dụng HEAVY sử dụng cơ sở dữ liệu 'tempdb' đó, bất kỳ 'hack' nào cũng có thể làm giảm sự tranh chấp trên các bảng hệ thống trong 'tempdb' là lợi ích; theo thời gian mọi thứ đã được cải thiện ... nhiều cơ sở dữ liệu tạm thời, khóa cấp hàng trên bảng hệ thống, phương thức phân bổ tốt hơn, v.v.

Trong khi đó, việc sử dụng truncate tablekhông làm tổn hại gì nếu để lại mã.


-2

Thật ý nghĩa khi thực hiện TRUNCATE cho các bảng có khóa ngoại. Tuy nhiên, đối với các bảng tạm thời chỉ cần DROP là đủ


TRUNCATE bằng cách nào đó sẽ tránh được một cuộc xung đột khóa nước ngoài? Làm sao?
dùng259412

1
Sẽ viết một lỗi rằng có khóa ngoại
Evgeniy Gribkov

-8

Quan điểm của việc truncateđơn giản là loại bỏ mọi thứ trong bảng (một số chi tiết kỹ thuật dựa trên công cụ lưu trữ dữ liệu có thể hơi khác nhau) - bỏ qua việc ghi nhật ký nặng, v.v.

drop tableghi lại tất cả các thay đổi khi những thay đổi đang được thực hiện. Vì vậy, để ghi nhật ký tối thiểu và giảm tốc độ hệ thống vô dụng, tôi sẽ nghi ngờ một bảng rất lớn có thể bị cắt trước, sau đó bị hủy.

truncate có thể được gói trong một giao dịch (đó sẽ là lựa chọn an toàn nhất), tất nhiên, sẽ cho phép bạn quay trở lại hoạt động.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.