Почему DELETE оказывает длительное влияние на производительность?

20

В конце тестовый скрипт для сравнения производительности между переменной @table и таблицей #temp. Я думаю, что я настроил это правильно - временные характеристики производительности взяты вне команд DELETE / TRUNCATE. Результаты, которые я получаю, следующие (раз в миллисекундах).

@Table Variable  #Temp (delete)  #Temp (truncate)
---------------  --------------  ----------------
5723             5180            5506
15636            14746           7800
14506            14300           5583
14030            15460           5386
16706            16186           5360

Просто чтобы убедиться, что я в здравом уме, это показывает, что CURRENT_TIMESTAMP (aka GetDate()) берется во время оператора, а не пакета, поэтому не должно быть никакого взаимодействия между TRUNCATE / DELETE с SET @StartTime = CURRENT_TIMESTAMPоператором.

select current_timestamp
waitfor delay '00:00:04'
select current_timestamp

-----------------------
2012-10-21 11:29:20.290

-----------------------
2012-10-21 11:29:24.290

Это довольно последовательно при переходе между первым и последующим запусками, когда для очистки таблицы используется DELETE. Чего мне не хватает в моем понимании DELETE ? Я повторял это много раз, менял порядок, изменял размер базы данных tempdb, чтобы не требовался рост и т. Д.

CREATE TABLE #values (
  id int identity primary key, -- will be clustered
  name varchar(100) null,
  number int null,
  type char(3) not null,
  low int null,
  high int null,
  status smallint not null
);
GO
SET NOCOUNT ON;

DECLARE @values TABLE (
  id int identity primary key clustered,
  name varchar(100) null,
  number int null,
  type char(3) not null,
  low int null,
  high int null,
  status smallint not null
);
DECLARE  @ExecutionTime  TABLE(      Duration bigINT    ) 
DECLARE  @StartTime DATETIME,  @i INT = 1; 
WHILE (@i <= 5) 
  BEGIN 
    DELETE @values;
    DBCC freeproccache With NO_InfoMSGS;
    DBCC DROPCLEANBUFFERS With NO_InfoMSGS;
    SET @StartTime = CURRENT_TIMESTAMP -- alternate getdate() 
    /****************** measured process ***********************/ 

    INSERT @values SELECT a.* FROM master..spt_values a join master..spt_values b on b.type='P' and b.number < 1000;

    /**************** end measured process *********************/ 
    INSERT @ExecutionTime 
    SELECT DurationInMilliseconds = datediff(ms,@StartTime,CURRENT_TIMESTAMP) 
    SET @i +=  1 
  END -- WHILE 

SELECT DurationInMilliseconds = Duration FROM   @ExecutionTime 
GO 

-- Temporary table
DECLARE  @ExecutionTime  TABLE(      Duration bigINT    ) 
DECLARE  @StartTime DATETIME,  @i INT = 1; 
WHILE (@i <= 5) 
  BEGIN 
    delete #values;
    -- TRUNCATE TABLE #values;
    DBCC freeproccache With NO_InfoMSGS;
    DBCC DROPCLEANBUFFERS With NO_InfoMSGS;
    SET @StartTime = CURRENT_TIMESTAMP -- alternate getdate() 
    /****************** measured process ***********************/ 

    INSERT #values SELECT a.* FROM master..spt_values a join master..spt_values b on b.type='P' and b.number < 1000;

    /**************** end measured process *********************/ 
    INSERT @ExecutionTime 
    SELECT DurationInMilliseconds = datediff(ms,@StartTime,CURRENT_TIMESTAMP) 
    SET @i +=  1 
  END -- WHILE 

SELECT DurationInMilliseconds = Duration FROM   @ExecutionTime 
GO

DROP TABLE  #values 
SET NOCOUNT OFF;
孔夫子
источник

Ответы:

20

Это различие, по-видимому, применимо только тогда, когда объект представляет собой дерево B +. При удалении primary keyпеременной на таблицу, так что это куча, я получил следующие результаты

2560
2120
2080
2130
2140

Но с PK я обнаружил похожую закономерность в моих тестах, а также типичные результаты ниже.

+--------+--------+---------+-------------------+
| @table | #table | ##table | [permanent_table] |
+--------+--------+---------+-------------------+
|   2670 |   2683 |    9603 |              9703 |
|   6823 |   6840 |    9723 |              9790 |
|   6813 |   6816 |    9626 |              9703 |
|   6883 |   6816 |    9600 |              9716 |
|   6840 |   6856 |    9610 |              9673 |
+--------+--------+---------+-------------------+

Моя теория заключается в том, что при массовых вставках в локальные временные деревья B + возможна некоторая оптимизация, которая применяется только тогда, когда у нее еще нет выделенных страниц.

Я основываю это на следующих наблюдениях.

  1. При выполнении различных версий тестового кода , который я только видел эту картину с @table_variablesи #tempтаблицы. Не постоянные таблицы tempdbни в ##таблицах.

  2. Чтобы получить более медленную производительность, необязательно предварительно добавлять и удалять большое количество строк из таблицы. Достаточно просто добавить одну строку и оставить ее там.

  3. TRUNCATEосвобождает все страницы из таблицы. DELETEне приведет к освобождению последней страницы в таблице.

  4. Использование профилировщика VS 2012 показывает, что в более быстром случае SQL Server использует другой путь кода. 36% времени затрачивается на sqlmin.dll!RowsetBulk::InsertRow61% времени, затраченного sqlmin.dll!RowsetNewSS::InsertRowна более медленный случай.

Бег

SELECT * 
FROM sys.dm_db_index_physical_stats(2,OBJECT_ID('tempdb..#values'),1,NULL, 'DETAILED')

после удаления возвращается

+-------------+------------+--------------+--------------------+
| index_level | page_count | record_count | ghost_record_count |
+-------------+------------+--------------+--------------------+
|           0 |          1 |            0 |                  1 |
|           1 |          1 |            1 |                  0 |
|           2 |          1 |            1 |                  0 |
+-------------+------------+--------------+--------------------+

Я обнаружил, что можно несколько уменьшить несоответствие времени, включив флаг трассировки 610 .

Это имело эффект уменьшения количества по регистрации , по существу , для последующих вставок ( по сравнению с 350 МБ до 103 МБ , как он больше не регистрирует отдельные вставленные значения строк) , но это было только улучшение незначительного в моменты времени для 2 - го и последующих @table, #tableслучаев и разрыв все еще остается. Флаг трассировки значительно улучшил общую производительность вставок для двух других типов таблиц.

+--------+--------+---------+-------------------+
| @table | #table | ##table | [permanent_table] |
+--------+--------+---------+-------------------+
|   2663 |   2670 |    5403 |              5426 |
|   5390 |   5396 |    5410 |              5403 |
|   5373 |   5390 |    5410 |              5403 |
|   5393 |   5410 |    5406 |              5433 |
|   5386 |   5396 |    5390 |              5420 |
+--------+--------+---------+-------------------+

Просматривая журнал транзакций, я заметил, что начальные вставки в пустые локальные временные таблицы кажутся даже более минимально зарегистрированными (при 96 МБ).

Примечательно, что эти более быстрые вставки имели только 657транзакции ( LOP_BEGIN_XACT/ LOP_COMMIT_XACTпары) по сравнению с более 10,000поздними в более медленных случаях. В частности LOP_FORMAT_PAGEоперации кажутся значительно сокращенными. В более медленных случаях для каждой страницы таблицы (приблизительно 10,270) имеется запись в журнале транзакций по сравнению с 4такими записями в быстром случае.

Журнал, используемый во всех трех случаях, был следующим (я удалил записи журнала для обновлений системных базовых таблиц, чтобы уменьшить объем текста, но они все еще включены в итоговые данные)

Запись первой вставки против @table_var(96,5 МБ)

+-----------------------+----------+----------------------------------------------+---------------+---------+
|       Operation       | Context  |                AllocUnitName                 | Size in Bytes |   Cnt   |
+-----------------------+----------+----------------------------------------------+---------------+---------+
| LOP_BEGIN_XACT        | LCX_NULL | NULL                                         |         83876 |     658 |
| LOP_COMMIT_XACT       | LCX_NULL | NULL                                         |         34164 |     657 |
| LOP_CREATE_ALLOCCHAIN | LCX_NULL | NULL                                         |           120 |       3 |
| LOP_FORMAT_PAGE       | LCX_HEAP | dbo.#531856C7                                |            84 |       1 |
| LOP_FORMAT_PAGE       | LCX_IAM  | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |            84 |       1 |
| LOP_FORMAT_PAGE       | LCX_IAM  | dbo.#531856C7                                |            84 |       1 |
| LOP_FORMAT_PAGE       | LCX_IAM  | Unknown Alloc Unit                           |            84 |       1 |
| LOP_HOBT_DDL          | LCX_NULL | NULL                                         |           216 |       6 |
| LOP_HOBT_DELTA        | LCX_NULL | NULL                                         |           320 |       5 |
| LOP_IDENT_NEWVAL      | LCX_NULL | NULL                                         |     100240000 | 2506000 |
| LOP_INSERT_ROWS       | LCX_HEAP | dbo.#531856C7                                |            72 |       1 |
| LOP_MODIFY_ROW        | LCX_IAM  | dbo.#531856C7                                |            88 |       1 |
| LOP_MODIFY_ROW        | LCX_PFS  | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |        158592 |    1848 |
| LOP_MODIFY_ROW        | LCX_PFS  | dbo.#531856C7                                |            80 |       1 |
| LOP_MODIFY_ROW        | LCX_PFS  | Unknown Alloc Unit                           |        216016 |    2455 |
| LOP_SET_BITS          | LCX_GAM  | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |         84360 |    1406 |
| LOP_SET_BITS          | LCX_GAM  | Unknown Alloc Unit                           |        147120 |    2452 |
| LOP_SET_BITS          | LCX_IAM  | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |         84360 |    1406 |
| LOP_SET_BITS          | LCX_IAM  | Unknown Alloc Unit                           |        147120 |    2452 |
| Total                 | NULL     | NULL                                         |     101209792 | 2519475 |
+-----------------------+----------+----------------------------------------------+---------------+---------+

Регистрация последующих вставок TF 610 отключена (350 МБ)

+-----------------------+--------------------+----------------------------------------------+---------------+---------+
|       Operation       |      Context       |                AllocUnitName                 | Size in Bytes |   Cnt   |
+-----------------------+--------------------+----------------------------------------------+---------------+---------+
| LOP_BEGIN_CKPT        | LCX_NULL           | NULL                                         |            96 |       1 |
| LOP_BEGIN_XACT        | LCX_NULL           | NULL                                         |       1520696 |   12521 |
| LOP_COMMIT_XACT       | LCX_NULL           | NULL                                         |        651040 |   12520 |
| LOP_CREATE_ALLOCCHAIN | LCX_NULL           | NULL                                         |            40 |       1 |
| LOP_DELETE_SPLIT      | LCX_INDEX_INTERIOR | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |          2160 |      36 |
| LOP_END_CKPT          | LCX_NULL           | NULL                                         |           136 |       1 |
| LOP_FORMAT_PAGE       | LCX_HEAP           | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |        859236 |   10229 |
| LOP_FORMAT_PAGE       | LCX_IAM            | Unknown Alloc Unit                           |            84 |       1 |
| LOP_FORMAT_PAGE       | LCX_INDEX_INTERIOR | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |          3108 |      37 |
| LOP_HOBT_DDL          | LCX_NULL           | NULL                                         |           648 |      18 |
| LOP_HOBT_DELTA        | LCX_NULL           | NULL                                         |        657088 |   10267 |
| LOP_IDENT_NEWVAL      | LCX_NULL           | NULL                                         |     100239960 | 2505999 |
| LOP_INSERT_ROWS       | LCX_CLUSTERED      | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |     258628000 | 2506000 |
| LOP_INSERT_ROWS       | LCX_HEAP           | dbo.#531856C7                                |            72 |       1 |
| LOP_INSERT_ROWS       | LCX_INDEX_INTERIOR | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |       1042776 |   10302 |
| LOP_MODIFY_HEADER     | LCX_HEAP           | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |        859236 |   10229 |
| LOP_MODIFY_HEADER     | LCX_INDEX_INTERIOR | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |          3192 |      38 |
| LOP_MODIFY_ROW        | LCX_IAM            | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |           704 |       8 |
| LOP_MODIFY_ROW        | LCX_PFS            | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |        934264 |   11550 |
| LOP_MODIFY_ROW        | LCX_PFS            | Unknown Alloc Unit                           |        783984 |    8909 |
| LOP_SET_BITS          | LCX_GAM            | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |         76980 |    1283 |
| LOP_SET_BITS          | LCX_GAM            | Unknown Alloc Unit                           |        534480 |    8908 |
| LOP_SET_BITS          | LCX_IAM            | dbo.#4F47C5E3.PK__#4F47C5E__3213E83F51300E55 |         76980 |    1283 |
| LOP_SET_BITS          | LCX_IAM            | Unknown Alloc Unit                           |        534480 |    8908 |
| LOP_SHRINK_NOOP       | LCX_NULL           | NULL                                         |            32 |       1 |
| LOP_XACT_CKPT         | LCX_NULL           | NULL                                         |            92 |       1 |
| Total                 | NULL               | NULL                                         |     367438748 | 5119297 |
+-----------------------+--------------------+----------------------------------------------+---------------+---------+

Регистрация последующих вставок TF 610 на (103 МБ)

+-------------------------+-------------------------+----------------------------------------------+---------------+---------+
|        Operation        |         Context         |                AllocUnitName                 | Size in Bytes |   Cnt   |
+-------------------------+-------------------------+----------------------------------------------+---------------+---------+
| LOP_BEGIN_CKPT          | LCX_NULL                | NULL                                         |           192 |       2 |
| LOP_BEGIN_XACT          | LCX_NULL                | NULL                                         |       1339796 |   11099 |
| LOP_BULK_EXT_ALLOCATION | LCX_NULL                | NULL                                         |         20616 |     162 |
| LOP_COMMIT_XACT         | LCX_NULL                | NULL                                         |        577096 |   11098 |
| LOP_CREATE_ALLOCCHAIN   | LCX_NULL                | NULL                                         |            40 |       1 |
| LOP_DELETE_SPLIT        | LCX_INDEX_INTERIOR      | dbo.#6DCC4D03.PK__#6DCC4D0__3213E83F6FB49575 |          2160 |      36 |
| LOP_END_CKPT            | LCX_NULL                | NULL                                         |           272 |       2 |
| LOP_FORMAT_PAGE         | LCX_BULK_OPERATION_PAGE | dbo.#6DCC4D03.PK__#6DCC4D0__3213E83F6FB49575 |        863520 |   10280 |
| LOP_FORMAT_PAGE         | LCX_IAM                 | Unknown Alloc Unit                           |            84 |       1 |
| LOP_FORMAT_PAGE         | LCX_INDEX_INTERIOR      | dbo.#6DCC4D03.PK__#6DCC4D0__3213E83F6FB49575 |          3108 |      37 |
| LOP_HOBT_DELTA          | LCX_NULL                | NULL                                         |        666496 |   10414 |
| LOP_IDENT_NEWVAL        | LCX_NULL                | NULL                                         |     100239960 | 2505999 |
| LOP_INSERT_ROWS         | LCX_CLUSTERED           | dbo.#6DCC4D03.PK__#6DCC4D0__3213E83F6FB49575 |         23544 |     218 |
| LOP_INSERT_ROWS         | LCX_HEAP                | dbo.#719CDDE7                                |            72 |       1 |
| LOP_INSERT_ROWS         | LCX_INDEX_INTERIOR      | dbo.#6DCC4D03.PK__#6DCC4D0__3213E83F6FB49575 |       1042776 |   10302 |
| LOP_MODIFY_HEADER       | LCX_BULK_OPERATION_PAGE | dbo.#6DCC4D03.PK__#6DCC4D0__3213E83F6FB49575 |        780216 |   10266 |
| LOP_MODIFY_HEADER       | LCX_HEAP                | dbo.#6DCC4D03.PK__#6DCC4D0__3213E83F6FB49575 |       1718472 |   20458 |
| LOP_MODIFY_HEADER       | LCX_INDEX_INTERIOR      | dbo.#6DCC4D03.PK__#6DCC4D0__3213E83F6FB49575 |          3192 |      38 |
| LOP_MODIFY_ROW          | LCX_IAM                 | dbo.#6DCC4D03.PK__#6DCC4D0__3213E83F6FB49575 |           704 |       8 |
| LOP_MODIFY_ROW          | LCX_PFS                 | dbo.#6DCC4D03.PK__#6DCC4D0__3213E83F6FB49575 |        114832 |    1307 |
| LOP_MODIFY_ROW          | LCX_PFS                 | Unknown Alloc Unit                           |        231696 |    2633 |
| LOP_RANGE_INSERT        | LCX_NULL                | NULL                                         |            48 |       1 |
| LOP_SET_BITS            | LCX_GAM                 | dbo.#6DCC4D03.PK__#6DCC4D0__3213E83F6FB49575 |         77100 |    1285 |
| LOP_SET_BITS            | LCX_GAM                 | Unknown Alloc Unit                           |        157920 |    2632 |
| LOP_SET_BITS            | LCX_IAM                 | dbo.#6DCC4D03.PK__#6DCC4D0__3213E83F6FB49575 |         77100 |    1285 |
| LOP_SET_BITS            | LCX_IAM                 | Unknown Alloc Unit                           |        157920 |    2632 |
| LOP_XACT_CKPT           | LCX_NULL                | NULL                                         |            92 |       1 |
| Total                   | NULL                    | NULL                                         |     108102960 | 2602218 |
+-------------------------+-------------------------+----------------------------------------------+---------------+---------+
Мартин Смит
источник
Спасибо за подробное подтверждение. Таким образом, вопрос все еще остается открытым: почему DELETE не возвращает таблицу по- настоящему пустой, используя ваш термин. Кроме того, это может привести к использованию таблиц #temp, если в цикле пакетной обработки используется clear / populate.
孔夫子
1
@RichardTheKiwi - Преимущество использования TRUNCATEболее DELETEпо себе также будет доказывать это. Я бы также редко рассматривал переменные таблицы для большого количества строк.
Мартин Смит
Это будет звучать лениво, но разве повторение вставки с 1 по 10 записей (переменная) 1000 раз в партии не показывает те же симптомы? Использование большого количества строк только усугубляет проблему и обеспечивает масштаб, чтобы лучше видеть разницу. Суть вопроса состоит в том, чтобы так или иначе доказать, что таблицы #temp будут лучше, как только мы узнаем, в чем разница.
孔夫子
Что ж, моя теория заключается в том, что распределение 10,000+страниц происходит гораздо более оптимизированным образом и, похоже, позволяет избежать некоторых затрат на страницу. Для небольших вставок я бы ожидал, что любая такая разница будет менее значимой.
Мартин Смит
@RichardTheKiwi - Спасибо! Наверное, есть что сказать по этому поводу. Как я попытаюсь обновить до той же версии, что и SQL Kiwi, и посмотрю, вижу ли я другие пути кода. Если это так, возможно, это зависит от аппаратного обеспечения, что это имеет значение (мои тесты были на моем настольном ПК со всеми данными и файлами журналов на одном и том же SSD)
Martin Smith,
0

Наблюдение и спекуляция. , ,

В некоторых системах CURRENT_TIMESTAMP определяется как время в начале текущей транзакции. При быстром поиске не было получено точной документации о том, как CURRENT_TIMESTAMP ведет себя на SQL Server. Но режим SQL Server по умолчанию - это автоматическая фиксация транзакций, и здесь нет BEGIN TRANSACTION, так что это должно быть время непосредственно перед оператором INSERT. (Оператор DELETE должен автоматически фиксироваться, и какой бы метод CURRENT_TIMESTAMP работал на SQL Server, он не должен иметь ничего общего с оператором DELETE, когда вы используете автоматически зафиксированные транзакции.)

На первой итерации оператор DELETE не имеет никакой реальной работы, и нет никаких отдельных строк для регистрации. Возможно, оптимизатор это знает, и это сокращает время первой итерации. (Сочетание отсутствия строк для удаления и отдельных строк для регистрации.)

Вы можете проверить это (я думаю), вставив перед удалением.

Майк Шеррилл 'Cat Recall'
источник
Я собираюсь перестать отвечать на вопросы сегодня. Или что я делаю, когда набираю вещи в эту коробку.
Майк Шеррилл 'Cat Recall'
Должен ли этот ответ быть удален как устаревший, тангенциальный и отвлекающий?
孔夫子