Навіщо використовувати TRUNCATE та DROP?


100

У системі, над якою я працюю, є багато збережених процедур та SQL-скриптів, які використовують тимчасові таблиці. Після використання цих таблиць корисно їх скинути.

Багато моїх колег (майже всі вони набагато досвідченіші, ніж я), як правило, роблять це:

TRUNCATE TABLE #mytemp
DROP TABLE #mytemp

Зазвичай я використовую сингл DROP TABLEу своїх сценаріях.

Чи є якісь вагомі причини зробити це TRUNCATEбезпосередньо перед DROP?

Відповіді:


130

Немає.

TRUNCATEі DROPмайже однакові за поведінкою та швидкістю, тому робити TRUNCATEправо перед a DROPпросто не потрібно.


Примітка. Цю відповідь я написав з точки зору SQL Server і припустив, що вона однаково стосується Sybase. Схоже, це не зовсім так .

Примітка: Коли я вперше опублікував цю відповідь, було кілька інших високо оцінених відповідей - включаючи прийняту тоді відповідь - які висували кілька помилкових тверджень, таких як: TRUNCATEне реєструється; TRUNCATEне можна відкотити назад; TRUNCATEшвидше, ніж DROP; тощо.

Тепер, коли ця тема була очищена, наступні спростування можуть здатися дотичними для початкового питання. Я залишаю їх тут як орієнтир для інших, хто прагне розвінчати ці міфи.


Існує декілька популярних брехливих прав, поширених навіть серед досвідчених спеціалістів з обмеженої відповідальності, які, можливо, мотивували цю TRUNCATE-then-DROPмодель. Вони є:

  • Міф : TRUNCATEне реєструється, тому його не можна повернути назад.
  • Міф : TRUNCATEшвидше, ніж DROP.

Дозвольте спростувати ці неправди. Я пишу це спростування з точки зору SQL Server, але все, що я тут кажу, повинно бути однаково застосовно до Sybase.

TRUNCATE є зареєстрованим, і він може бути проведений відкат.

  • TRUNCATEє зареєстрованою операцією, тому її можна повернути назад . Просто загорніть його в транзакцію.

    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;
    

    Однак зауважте, що це не стосується Oracle . Хоча зафіксований та захищений функцією скасування та повторення Oracle TRUNCATEта іншими операторами DDL користувач не може повернути назад, оскільки проблеми Oracle неявно виконує безпосередньо перед і після всіх заяв DDL.

  • TRUNCATEмінімально зареєстрований , на відміну від повністю зареєстрованих. Що це означає? Скажіть вам TRUNCATEстіл. Замість того, щоб кожен видалений рядок містити в журналі транзакцій, TRUNCATEпросто позначає сторінки даних, на яких вони живуть, як нерозподілені. Тому так швидко. Ось чому ви не можете відновити рядки TRUNCATEтаблиці -ed з журналу транзакцій за допомогою зчитувача журналів. Все, що ви знайдете там, - це посилання на розроблені сторінки даних.

    Порівняйте це з DELETE. Якщо ви маєте DELETEвсі рядки в таблиці та здійснюєте транзакцію, то теоретично ви все одно можете знайти видалені рядки в журналі транзакцій та відновити їх звідти. Це тому, що DELETEзаписує кожен видалений рядок у журнал транзакцій. Для великих таблиць це зробить набагато повільніше, ніж TRUNCATE.

DROP так само швидко, як і TRUNCATE.

  • Мовляв TRUNCATE, DROPце мінімально зареєстрована операція. Це означає, що DROPможна також відкотити. Це також означає, що вона працює точно так само, як і TRUNCATE. Замість видалення окремих рядків DROPпозначте відповідні сторінки даних як нерозподілені та додатково позначте метадані таблиці як видалені .
  • Оскільки TRUNCATEі DROPпрацюють точно так само, вони бігають так само швидко, як один одного. Немає сенсу TRUNCATE-ing таблицю перед DROP-ing. Запустіть цей демо-скрипт у своєму екземплярі розробки, якщо ви мені не вірите.

    На моїй локальній машині з теплим кешем отримані результати такі:

    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
    

    Отже, для 134- мільйонної таблиці рядків і те, DROPі TRUNCATEвзагалі не вимагає часу. (У холодному кеші вони займають приблизно 2-3 секунди для першого запуску або двох.) Я також вважаю, що більша середня тривалість для TRUNCATEтодішньої DROPоперації пояснюється варіаціями завантаження на моїй локальній машині, а не тому, що комбінація якось магічно порядок гірший, ніж окремі операції. Зрештою, вони майже точно те саме.

    Якщо вас цікавить детальніше про ведення журналу цих операцій, Мартін має чітке пояснення цього.


52

Тестування, TRUNCATEа потім DROPпросто DROPпряме дослідження, показує, що перший підхід насправді має невеликі збільшення витрат на лісозаготівлю, тому навіть може бути м'яко протилежним продуктивним.

Переглядаючи окремі записи журналів, видно, що TRUNCATE ... DROPверсія майже ідентична DROPверсії, за винятком цих додаткових записів.

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

Таким чином, TRUNCATEперша версія закінчує витрачати трохи зусиль, роблячи деякі оновлення різних системних таблиць наступним чином

  • Оновлення rcmodifiedдля всіх стовпців таблиці вsys.sysrscols
  • Оновити rcrowsвsysrowsets
  • Обнулити pgfirst, pgroot, pgfirstiam, pcused, pcdata, pcreservedвsys.sysallocunits

Ці рядки системної таблиці видаляються лише тоді, коли таблиця випадає в наступному операторі.

Повний розрив вниз каротажу , проведеної TRUNCATEVS DROPнижче. Я також додав 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        |
+-------------------+-------------------+--------------------+------------------+-----------+---------------+-------------+------------------+-----------+---------------+-------------+

Тест проводився в базі даних з повною моделлю відновлення проти таблиці 1000 рядків з одним рядком на сторінці. Таблиця споживає 1 004 сторінки за рахунок кореневої індексної сторінки та 3 індексних сторінок проміжного рівня.

8 з цих сторінок - це виділення однієї сторінки в змішаних розширеннях, а решта розподілена по 125 Уніфікованих розширень. Розподіл 8 окремих сторінок відображається як 8 LOP_MODIFY_ROW,LCX_IAMзаписів журналу. 125 ступеня розселення як LOP_SET_BITS LCX_GAM,LCX_IAM. Обидві ці операції також потребують оновлення пов’язаної PFSсторінки, отже, об'єднаних 133 LOP_MODIFY_ROW, LCX_PFSзаписів. Потім, коли таблиця фактично випала, метадані про неї потрібно видалити з різних системних таблиць, отже, 22 LOP_DELETE_ROWSзаписи журналу системної таблиці (враховуються як нижче)

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

Повний сценарій нижче

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

Добре думав, що я спробую зробити деякі орієнтири, які не покладаються на жодне "тепле кешування", щоб, сподіваємось, вони були більш реалістичним тестом (також використовуючи Postgres, щоб побачити, чи відповідає він тим же характеристикам інших опублікованих відповідей) :

Мої орієнтири, що використовують postgres 9.3.4 із великою базою даних ish, (сподіваємось, достатньо великою, щоб не вміститися в кеш-пам'яті оперативної пам'яті):

Використовуючи цей тестовий сценарій БД: https://gist.github.com/rdp/8af84fbb54a430df8fc0

з 10М рядками:

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

зі 100М рядками:

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

Отже, з цього припускаю, що падіння є "приблизно" настільки ж швидким (або швидшим), як усікання + падіння (принаймні для сучасних версій Postgres), однак, якщо ви плануєте також розвернутися та відтворити таблицю, ви можете: добре триматися прямого усічення, що швидше, ніж крапля + відтворити (має сенс). FWIW.

примітка 1: https://stackoverflow.com/questions/11419536/postgresql-truncation-speed/11423886#11423886 (говорить, що постгреси 9.2 можуть мати швидший скорочення, ніж попередні версії). Як завжди, орієнтуйтеся на власну систему, щоб побачити її характеристики.

Примітка 2: усікання може бути повернуте назад у postgres, якщо в транзакції: http://www.postgresql.org/docs/8.4/static/sql-truncate.html

Примітка 3: усічення може з невеликими таблицями іноді бути повільніше, ніж видалення: https://stackoverflow.com/questions/11419536/postgresql-truncation-speed/11423886#11423886


1

Додавання певної історичної точки зору ...

Видалення таблиці вимагає оновлення декількох системних таблиць, що, в свою чергу, зазвичай вимагає внесення цих змін у системну таблицю в одну транзакцію (подумайте "почати тран, видалити колонки, видалити сисобекти, здійснити").

Також в "таблицю випадок" входить необхідність розміщення всіх сторінок даних / покажчиків, пов'язаних з таблицею.

Багато, багато, багато років тому ... процес транслокації простору був включений в транзакцію, яка також оновлювала системні таблиці; чистим результатом було те, що чим більша кількість виділених сторінок, тим більше часу займає розміщення зазначених сторінок, тим довше транзакція (на системних таблицях) залишалася відкритою, і, таким чином, більша ймовірність блокування (на системних таблицях) інших процесів, які намагаються створити / відкинути таблиці в tempdb (особливо неприємно зі старими блокуваннями allpages == сторінка та потенціал для таблиці -ескалація рівня блокування).

Одним із ранніх методів, що використовувались (для того, щоб зменшити суперечки в системних таблицях), було скоротити час утримування замків на системних таблицях, і один (відносно) простий спосіб зробити це - розміщення сторінок даних / покажчиків перед тим, як скинути стіл.

Хоча truncate tableне розміщує всіх сторінок даних / покажчиків, він розміщує всі, крім однієї 8-сторінкової (дані) міри; ще один "хак" полягав у тому, щоб потім скинути всі індекси перед тим, як скинути таблицю (так, розділити txn на sysindexes, але менший txn для таблиці drop).

Якщо врахувати, що (знову багато, багато років тому) існувала лише одна база даних «tempdb», і деякі додатки ГОЛОВО використовували ту єдину базу даних «tempdb», будь-які «хаки», які могли б зменшити суперечки щодо системних таблиць у 'tempdb' принесли користь; з часом все покращилося ... кілька тимчасових баз даних, блокування на рівні рядків у системних таблицях, кращі методи делокації тощо.

Тим часом використання цього truncate tableнічого не зашкодить, якщо його залишити в коді.



-8

Сенс truncateполягає в тому, щоб просто та безповоротно видалити все з таблиці (деякі технічні особливості, засновані на двигунах сховища даних, можуть дещо відрізнятися) - пропуск важких журналів тощо.

drop tableзаписує всі зміни під час внесення змін. Отже, щоб мати мінімальну реєстрацію та зменшити непотрібну систему, я б підозрював, що дуже велику таблицю можна спочатку обрізати, а потім скинути.

truncate може бути завернуто в транзакцію (що було б найбезпечнішим варіантом), що, звичайно, дозволить вам відкотити операцію.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.