Вопрос

Я думал, что это было решено с помощью ссылки ниже - обходной путь работает - но патч - нет.Работаем со службой поддержки Microsoft для решения.

http://support.microsoft.com/kb/2606883

Итак, у меня есть проблема, которую я хотел бы сообщить в StackOverflow, чтобы узнать, есть ли у кого-нибудь идеи.

Обратите внимание, что это касается SQL Server 2008 R2.

Проблема:Удаление 3000 записей из таблицы с 15000 записями занимает 3-4 минуты при включенном триггере и всего 3-5 секунд при отключенном триггере.

Настройка стола

Две таблицы мы назовем Main и Secondary.Вторичная таблица содержит записи элементов, которые я хочу удалить, поэтому при выполнении удаления я присоединяюсь к вторичной таблице.Перед оператором удаления запускается процесс заполнения вторичной таблицы записями, которые необходимо удалить.

Удалить заявление:

DELETE FROM MAIN 
WHERE ID IN (
   SELECT Secondary.ValueInt1 
   FROM Secondary 
   WHERE SECONDARY.GUID = '9FFD2C8DD3864EA7B78DA22B2ED572D7'
);

В этой таблице много столбцов и около 14 различных индексов NC.Я перепробовал кучу разных вещей, прежде чем понял, что проблема в триггере.

  • Включите блокировку страниц (у нас отключена по умолчанию)
  • Собранная статистика вручную
  • Отключен автосбор статистики
  • Подтвержденное состояние и фрагментация индекса
  • Удален кластерный индекс из таблицы
  • Изучил план выполнения (ничего не отображалось как отсутствующие индексы, а затраты составили 70 процентов на фактическое удаление и около 28 процентов на объединение/объединение записей).

Триггеры

Таблица имеет три триггера (по одному для операций вставки, обновления и удаления).Я изменил код триггера удаления, чтобы он просто возвращался, а затем выбирал его и смотрел, сколько раз он срабатывает.Он срабатывает только один раз за всю операцию (как и ожидалось).

ALTER TRIGGER [dbo].[TR_MAIN_RD] ON [dbo].[MAIN]
            AFTER DELETE
            AS  
                SELECT 1
                RETURN

Подводя итоги

  • При включенном триггере выполнение запроса занимает 3–4 минуты.
  • При выключенном триггере выполнение запроса занимает 3–5 секунд.

У кого-нибудь есть идеи, почему?

Также обратите внимание: не собираюсь менять эту архитектуру, добавлять индексы удаления и т. д.как решение.Эта таблица является центральной частью некоторых основных операций с данными, и нам пришлось настроить ее (индексы, блокировку страниц и т. д.), чтобы основные параллельные операции могли работать без взаимоблокировок.

Вот план казни в формате xml (имена изменены в целях защиты невиновных)

<?xml version="1.0" encoding="utf-16"?>
<ShowPlanXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Version="1.1" Build="10.50.1790.0" xmlns="http://schemas.microsoft.com/sqlserver/2004/07/showplan">
  <BatchSequence>
    <Batch>
      <Statements>
        <StmtSimple StatementCompId="1" StatementEstRows="185.624" StatementId="1" StatementOptmLevel="FULL" StatementOptmEarlyAbortReason="GoodEnoughPlanFound" StatementSubTreeCost="0.42706" StatementText="DELETE FROM MAIN WHERE ID IN (SELECT Secondary.ValueInt1 FROM Secondary WHERE Secondary.SetTMGUID = '9DDD2C8DD3864EA7B78DA22B2ED572D7')" StatementType="DELETE" QueryHash="0xAEA68D887C4092A1" QueryPlanHash="0x78164F2EEF16B857">
          <StatementSetOptions ANSI_NULLS="true" ANSI_PADDING="true" ANSI_WARNINGS="true" ARITHABORT="false" CONCAT_NULL_YIELDS_NULL="true" NUMERIC_ROUNDABORT="false" QUOTED_IDENTIFIER="true" />
          <QueryPlan CachedPlanSize="48" CompileTime="20" CompileCPU="20" CompileMemory="520">
            <RelOp AvgRowSize="9" EstimateCPU="0.00259874" EstimateIO="0.296614" EstimateRebinds="0" EstimateRewinds="0" EstimateRows="185.624" LogicalOp="Delete" NodeId="0" Parallel="false" PhysicalOp="Clustered Index Delete" EstimatedTotalSubtreeCost="0.42706">
              <OutputList />
              <Update WithUnorderedPrefetch="true" DMLRequestSort="false">
                <Object Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Index="[IX_MAIN_02]" IndexKind="Clustered" />
                <Object Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Index="[PK_MAIN_ID]" IndexKind="NonClustered" />
                <Object Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Index="[UK_MAIN_01]" IndexKind="NonClustered" />
                <Object Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Index="[IX_MAIN_03]" IndexKind="NonClustered" />
                <Object Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Index="[IX_MAIN_04]" IndexKind="NonClustered" />
                <Object Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Index="[IX_MAIN_05]" IndexKind="NonClustered" />
                <Object Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Index="[IX_MAIN_06]" IndexKind="NonClustered" />
                <Object Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Index="[IX_MAIN_07]" IndexKind="NonClustered" />
                <Object Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Index="[IX_MAIN_08]" IndexKind="NonClustered" />
                <Object Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Index="[IX_MAIN_09]" IndexKind="NonClustered" />
                <Object Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Index="[IX_MAIN_10]" IndexKind="NonClustered" />
                <Object Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Index="[IX_MAIN_11]" IndexKind="NonClustered" />
                <Object Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Index="[UK_MAIN_12]" IndexKind="NonClustered" />
                <Object Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Index="[IX_MAIN_13]" IndexKind="NonClustered" />
                <RelOp AvgRowSize="15" EstimateCPU="1.85624E-05" EstimateIO="0" EstimateRebinds="0" EstimateRewinds="0" EstimateRows="185.624" LogicalOp="Top" NodeId="2" Parallel="false" PhysicalOp="Top" EstimatedTotalSubtreeCost="0.127848">
                  <OutputList>
                    <ColumnReference Column="Uniq1002" />
                    <ColumnReference Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Column="RelationshipID" />
                  </OutputList>
                  <Top RowCount="true" IsPercent="false" WithTies="false">
                    <TopExpression>
                      <ScalarOperator ScalarString="(0)">
                        <Const ConstValue="(0)" />
                      </ScalarOperator>
                    </TopExpression>
                    <RelOp AvgRowSize="15" EstimateCPU="0.0458347" EstimateIO="0" EstimateRebinds="0" EstimateRewinds="0" EstimateRows="185.624" LogicalOp="Left Semi Join" NodeId="3" Parallel="false" PhysicalOp="Merge Join" EstimatedTotalSubtreeCost="0.12783">
                      <OutputList>
                        <ColumnReference Column="Uniq1002" />
                        <ColumnReference Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Column="RelationshipID" />
                      </OutputList>
                      <Merge ManyToMany="false">
                        <InnerSideJoinColumns>
                          <ColumnReference Database="[MyDatabase]" Schema="[dbo]" Table="[Secondary]" Column="ValueInt1" />
                        </InnerSideJoinColumns>
                        <OuterSideJoinColumns>
                          <ColumnReference Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Column="ID" />
                        </OuterSideJoinColumns>
                        <Residual>
                          <ScalarOperator ScalarString="[MyDatabase].[dbo].[MAIN].[ID]=[MyDatabase].[dbo].[Secondary].[ValueInt1]">
                            <Compare CompareOp="EQ">
                              <ScalarOperator>
                                <Identifier>
                                  <ColumnReference Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Column="ID" />
                                </Identifier>
                              </ScalarOperator>
                              <ScalarOperator>
                                <Identifier>
                                  <ColumnReference Database="[MyDatabase]" Schema="[dbo]" Table="[Secondary]" Column="ValueInt1" />
                                </Identifier>
                              </ScalarOperator>
                            </Compare>
                          </ScalarOperator>
                        </Residual>
                        <RelOp AvgRowSize="19" EstimateCPU="0.0174567" EstimateIO="0.0305324" EstimateRebinds="0" EstimateRewinds="0" EstimateRows="15727" LogicalOp="Index Scan" NodeId="4" Parallel="false" PhysicalOp="Index Scan" EstimatedTotalSubtreeCost="0.0479891" TableCardinality="15727">
                          <OutputList>
                            <ColumnReference Column="Uniq1002" />
                            <ColumnReference Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Column="ID" />
                            <ColumnReference Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Column="RelationshipID" />
                          </OutputList>
                          <IndexScan Ordered="true" ScanDirection="FORWARD" ForcedIndex="false" ForceSeek="false" NoExpandHint="false">
                            <DefinedValues>
                              <DefinedValue>
                                <ColumnReference Column="Uniq1002" />
                              </DefinedValue>
                              <DefinedValue>
                                <ColumnReference Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Column="ID" />
                              </DefinedValue>
                              <DefinedValue>
                                <ColumnReference Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Column="RelationshipID" />
                              </DefinedValue>
                            </DefinedValues>
                            <Object Database="[MyDatabase]" Schema="[dbo]" Table="[MAIN]" Index="[PK_MAIN_ID]" IndexKind="NonClustered" />
                          </IndexScan>
                        </RelOp>
                        <RelOp AvgRowSize="11" EstimateCPU="0.00392288" EstimateIO="0.03008" EstimateRebinds="0" EstimateRewinds="0" EstimateRows="3423.53" LogicalOp="Index Seek" NodeId="5" Parallel="false" PhysicalOp="Index Seek" EstimatedTotalSubtreeCost="0.0340029" TableCardinality="171775">
                          <OutputList>
                            <ColumnReference Database="[MyDatabase]" Schema="[dbo]" Table="[Secondary]" Column="ValueInt1" />
                          </OutputList>
                          <IndexScan Ordered="true" ScanDirection="FORWARD" ForcedIndex="false" ForceSeek="false" NoExpandHint="false">
                            <DefinedValues>
                              <DefinedValue>
                                <ColumnReference Database="[MyDatabase]" Schema="[dbo]" Table="[Secondary]" Column="ValueInt1" />
                              </DefinedValue>
                            </DefinedValues>
                            <Object Database="[MyDatabase]" Schema="[dbo]" Table="[Secondary]" Index="[IX_Secondary_01]" IndexKind="NonClustered" />
                            <SeekPredicates>
                              <SeekPredicateNew>
                                <SeekKeys>
                                  <Prefix ScanType="EQ">
                                    <RangeColumns>
                                      <ColumnReference Database="[MyDatabase]" Schema="[dbo]" Table="[Secondary]" Column="SetTMGUID" />
                                    </RangeColumns>
                                    <RangeExpressions>
                                      <ScalarOperator ScalarString="'9DDD2C8DD3864EA7B78DA22B2ED572D7'">
                                        <Const ConstValue="'9DDD2C8DD3864EA7B78DA22B2ED572D7'" />
                                      </ScalarOperator>
                                    </RangeExpressions>
                                  </Prefix>
                                </SeekKeys>
                              </SeekPredicateNew>
                            </SeekPredicates>
                          </IndexScan>
                        </RelOp>
                      </Merge>
                    </RelOp>
                  </Top>
                </RelOp>
              </Update>
            </RelOp>
          </QueryPlan>
        </StmtSimple>
      </Statements>
    </Batch>
  </BatchSequence>
</ShowPlanXML>
Это было полезно?

Решение 2

Ну, вот официальный ответ от Microsoft ... который, я думаю, является основным недостатком дизайна.

14.11.2011 - Официальный ответ изменился. Они не используют журнал транзакций, как указано ранее. Используют внутренний магазин (уровень строки) для копирования измененных данных. Они до сих пор не могут определить, почему это заняло так долго.

Мы решили использовать вместо триггеров вместо после удаления триггеров.

После того, как часть триггера заставляет нас прочитать через журнал транзакций после завершения удалений и создать вставленную/удаленную таблицу триггера. Именно здесь мы проводим огромное количество времени и занимаемся замыслом для последующей части триггера. Вместо триггера предотвратит такое поведение сканирования журнала транзакций и создания вставленной/удаленной таблицы. Кроме того, поскольку было замечено, что вещи намного быстрее, если мы бросаем все столбцы с помощью NVARCHAR (MAX), что имеет смысл из -за того, что они считаются данными LOB. Пожалуйста, попросите Alook в статье ниже для получения дополнительной информации относительно данных в строке:

http://msdn.microsoft.com/en-us/library/ms189087.aspx

Резюме: после триггера требуется сканирование через журнал транзакций после окончания удаления, затем мы должны создать и вставить/удаленную таблицу, которая требует большего использования журнала транзакций и времени.

Таким образом, в качестве плана действий, это то, что мы предлагаем в настоящее время:

A) Limit the number of rows deleted in each transaction or
B) Increase timeout settings or
C) Don't use AFTER trigger or trigger at all or
D) Limit usage of nvarchar(max) datatypes.

Другие советы

Структура управления версиями строк, представленная в SQL Server 2005, используется для поддержки ряда функций, включая новые уровни изоляции транзакций. READ_COMMITTED_SNAPSHOT и SNAPSHOT.Даже если ни один из этих уровней изоляции не включен, управление версиями строк по-прежнему используется для AFTER триггеры (чтобы облегчить генерацию inserted и deleted псевдотаблицы), MARS и (в отдельном хранилище версий) онлайн-индексирование.

Как задокументировано, механизм может добавить 14-байтовый постфикс к каждой строке таблицы, версии которой предназначены для любой из этих целей.Такое поведение относительно хорошо известно, как и добавление 14-байтовых данных в каждую строку индекса. восстановлен онлайн с включенным уровнем изоляции управления версиями строк.Даже если уровни изоляции не включены, к только некластеризованные индексы когда перестроен ONLINE.

Если присутствует триггер AFTER и в противном случае управление версиями привело бы к добавлению 14 байтов на строку, в движке существует оптимизация для избегать это, но где ROW_OVERFLOW или LOB распределение не может произойти.На практике это означает, что максимально возможный размер строки должен быть меньше 8060 байт.При расчете максимум возможные размеры строк, движок предполагает, например, что столбец VARCHAR(460) может содержать 460 символов.

Поведение легче всего увидеть с помощью AFTER UPDATE триггер, хотя тот же принцип применим и к AFTER DELETE.Следующий скрипт создает таблицу с максимальной длиной строки 8060 байт.Данные умещаются на одной странице, на которой имеется 13 байт свободного места.Существует неактивный триггер, поэтому страница разделяется и добавляется информация о версии:

USE Sandpit;
GO
CREATE TABLE dbo.Example
(
    ID          integer NOT NULL IDENTITY(1,1),
    Value       integer NOT NULL,
    Padding1    char(42) NULL,
    Padding2    varchar(8000) NULL,

    CONSTRAINT PK_Example_ID
    PRIMARY KEY CLUSTERED (ID)
);
GO
WITH
    N1 AS (SELECT 1 AS n UNION ALL SELECT 1),
    N2 AS (SELECT L.n FROM N1 AS L CROSS JOIN N1 AS R),
    N3 AS (SELECT L.n FROM N2 AS L CROSS JOIN N2 AS R),
    N4 AS (SELECT L.n FROM N3 AS L CROSS JOIN N3 AS R)
INSERT TOP (137) dbo.Example
    (Value)
SELECT
    ROW_NUMBER() OVER (ORDER BY (SELECT 0))
FROM N4;
GO
ALTER INDEX PK_Example_ID 
ON dbo.Example 
REBUILD WITH (FILLFACTOR = 100);
GO
SELECT
    ddips.index_type_desc,
    ddips.alloc_unit_type_desc,
    ddips.index_level,
    ddips.page_count,
    ddips.record_count,
    ddips.max_record_size_in_bytes
FROM sys.dm_db_index_physical_stats(DB_ID(), OBJECT_ID(N'dbo.Example', N'U'), 1, 1, 'DETAILED') AS ddips
WHERE
    ddips.index_level = 0;
GO
CREATE TRIGGER ExampleTrigger
ON dbo.Example
AFTER DELETE, UPDATE
AS RETURN;
GO
UPDATE dbo.Example
SET Value = -Value
WHERE ID = 1;
GO
SELECT
    ddips.index_type_desc,
    ddips.alloc_unit_type_desc,
    ddips.index_level,
    ddips.page_count,
    ddips.record_count,
    ddips.max_record_size_in_bytes
FROM sys.dm_db_index_physical_stats(DB_ID(), OBJECT_ID(N'dbo.Example', N'U'), 1, 1, 'DETAILED') AS ddips
WHERE
    ddips.index_level = 0;
GO
DROP TABLE dbo.Example;

Скрипт выдает результат, показанный ниже.Одностраничная таблица разбита на две страницы, и максимальная физический длина строки увеличилась с 57 до 71 байта (= +14 байт для информации о версии строки).

Update example

DBCC PAGE показывает, что единственная обновленная строка имеет Record Attributes = NULL_BITMAP VERSIONING_INFO Record Size = 71, тогда как все остальные строки таблицы имеют Record Attributes = NULL_BITMAP; record Size = 57.

Тот же сценарий с UPDATE заменено одной строкой DELETE производит показанный результат:

DELETE dbo.Example
WHERE ID = 1;

Delete example

Всего на одну строку меньше (конечно!), но максимальный физический размер строки не увеличился.Информация о версии строк добавляется только к строкам, необходимым для псевдотаблиц триггера, и в конечном итоге эта строка была удалена.Однако разделение страниц остается.Это действие разделения страниц отвечает за низкую производительность, наблюдаемую при наличии триггера.Если определение Padding2 столбец изменен с varchar(8000) к varchar(7999), страница больше не разбивается.

Также посмотрите это Сообщение блога автор SQL Server MVP Дмитрий Короткевич, в котором также обсуждается влияние на фрагментацию.

Согласно плану, все идет правильно. Вы можете попробовать написать удаление как соединение вместо того, что даст вам другой план.

DELETE m
FROM MAIN m
JOIN Secondary s ON m.ID = s.ValueInt1
AND s.SetTMGUID = '9DDD2C8DD3864EA7B78DA22B2ED572D7'

Я не уверен, сколько это поможет. Когда удаление работает с триггерами на таблице, какой тип ожидания для сеанса делает удаление?

Лицензировано под: CC-BY-SA с атрибуция
Не связан с dba.stackexchange
scroll top