Зачем использовать 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накрывать стол перед тем, как DROPего ставить. Запустите этот демонстрационный скрипт на вашем экземпляре разработки, если вы мне не верите.

    На моей локальной машине с теплым кэшем я получаю следующие результаты:

    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 с базой данных большого размера (надеюсь, достаточно большой, чтобы не помещаться в кэш-память ОЗУ):

Используя этот тестовый скрипт БД: 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)

с 100M строк:

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

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

примечание 1: https://stackoverflow.com/questions/11419536/postgresql-truncation-speed/11423886#11423886 (говорит, что postgres 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 для удаления таблицы).

Если учесть, что (опять-таки, много-много лет назад) была только одна база данных «tempdb», и некоторые приложения HEAVY использовали эту единственную базу данных «tempdb», то есть любые «хаки», которые могли уменьшить конкуренцию в системных таблицах в «tempdb» были полезны; со временем ситуация улучшилась ... несколько временных баз данных, блокировка на уровне строк в системных таблицах, улучшенные методы освобождения и т. д.

Тем временем использование truncate tableничего не повредит, если его оставить в коде.


-2

Имеет смысл использовать TRUNCATE для таблиц с внешними ключами. Однако для временных таблиц достаточно DROP


TRUNCATE поможет избежать конфликта внешних ключей? Как?
user259412

1
Напишет ошибку, что есть внешний ключ
Евгений Грибков

-8

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

drop tableрегистрирует все изменения по мере их внесения. Таким образом, для минимального ведения журнала и уменьшения бесполезного оттока системы, я подозреваю, что очень большую таблицу можно сначала обрезать, а затем удалить.

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.