トリガーが有効な場合のレコードの削除が遅い


17

これは以下のリンクで解決されたと思います-回避策は動作します-しかし、パッチは動作しません。Microsoftサポートと協力して解決します。

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

わかりましたので、誰かがアイデアを持っているかどうかを確認するためにStackOverflowに捨てたい問題があります。

これはSQL Server 2008 R2でのことに注意してください

問題:15000レコードのテーブルから3000レコードを削除すると、トリガーが有効な場合は3〜4分かかり、トリガーが無効な場合は3〜5秒しかかかりません。

テーブルのセットアップ

メインとセカンダリと呼ばれる2つのテーブル。セカンダリには削除するアイテムのレコードが含まれているため、削除を実行するとセカンダリテーブルに参加します。deleteステートメントの前にプロセスが実行され、削除するレコードがセカンダリテーブルに入力されます。

ステートメントを削除:

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

このテーブルには多くの列と約14の異なるNCインデックスがあります。トリガーが問題であると判断する前に、さまざまなことを試しました。

  • ページロックをオンにします(デフォルトではオフになっています)
  • 手動で収集された統計
  • 統計の自動収集を無効にしました
  • 検証済みのインデックスの健全性と断片化
  • テーブルからクラスター化インデックスを削除しました
  • 実行計画を調査しました(インデックスの欠落として表示されるものはなく、実際の削除にかかるコストは70%で、レコードの結合/マージには約28%でした)

トリガー

テーブルには3つのトリガーがあります(挿入、更新、削除の各操作に1つ)。削除トリガーのコードを変更して、単に戻るようにした後、1つを選択して、トリガーされる回数を確認しました。操作全体で1回だけ起動します(予想どおり)。

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>

回答:


12

SQL Server 2005で導入された行バージョン管理フレームワークは、新しいトランザクション分離レベルREAD_COMMITTED_SNAPSHOTやを含む多くの機能をサポートするために使用されますSNAPSHOT。これらの分離レベルのいずれも有効になっていない場合でも、AFTERトリガー(insertedおよびdeleted擬似テーブルの生成を容易にするため)、MARS、および(別のバージョンストア内の)オンラインインデックスに行バージョン管理が使用されます。

文書、エンジンは、これらの目的のいずれかのバージョン管理されているテーブルの各行に14バイトの接尾辞を追加することができます。この動作は比較的よく知られています。また、行バージョン分離レベルを有効にしてオンライン再構築されるインデックスのすべての行に14バイトのデータが追加されます。分離レベルが有効になっていない場合でも、非クラスター化インデックスにのみ 1バイト追加されます場合でも、再構築時にされONLINEます。

AFTERトリガが存在し、バージョンが他の行ごとに14バイトを追加する場合、最適化は、エンジン内に存在避けるこれが、どこROW_OVERFLOWか、LOB割り当てが発生することができません。実際には、これは、行の最大可能サイズが8060バイト未満でなければならないことを意味します。最大値を計算する可能行サイズのでは、エンジンは、たとえばVARCHAR(460)列に460文字が含まれると想定しています。

AFTER UPDATE同じ原則がにも当てはまりますが、動作はトリガーを使用すると最もわかりやすくなりますAFTER DELETE。次のスクリプトは、行内の最大長が8060バイトのテーブルを作成します。データは1ページに収まり、そのページには13バイトの空き領域があります。no-opトリガーが存在するため、ページが分割され、バージョン情報が追加されます。

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;

スクリプトは、以下に示す出力を生成します。単一ページのテーブルは2ページに分割され、物理的な行の最大長は57バイトから71バイトに増えました(=行バージョン情報の場合は+14バイト)。

更新例

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;

例を削除

合計で行は1つ少なくなります(もちろんです!)が、物理的な最大行サイズは増加していません。行のバージョン管理情報は、トリガー疑似テーブルに必要な行にのみ追加され、その行は最終的に削除されました。ただし、ページ分割は残ります。このページ分割アクティビティは、トリガーが存在したときに観察されたパフォーマンスの低下の原因です。Padding2列の定義がからvarchar(8000)に変更されるvarchar(7999)と、ページは分割されなくなります。

また、SQL Server MVP Dmitri Korotkevitchによるこのブログ投稿も参照してください。このブログ投稿では、フラグメンテーションへの影響についても説明しています。


1
ああ、私は先日SOでこれについて質問しましたが、決定的な答えは得られませんでした。
マーティンスミス

5

さて、ここにマイクロソフトからの公式の回答があります...これは大きな設計上の欠陥だと思います。

2011/11/14-公式の対応が変更されました。前述のように、トランザクションログを使用していません。内部ストア(行レベル)を使用して、変更されたデータをコピーしています。彼らはそれがなぜそんなに時間がかかったのかまだ判断できません。

削除後トリガーの代わりに、代わりにトリガーを使用することにしました。

トリガーのAFTER部分により、削除が完了した後にトランザクションログを読み取って、挿入/削除されたトリガーテーブルを作成する必要があります。これは膨大な時間を費やす場所であり、トリガーのAFTER部分の仕様です。INSTEAD OFトリガーは、トランザクションログをスキャンし、挿入/削除されたテーブルを作成するこの動作を防ぎます。また、nvarchar(max)を使用してすべての列を削除すると、物事がはるかに高速になることが観察されましたが、これはLOBデータと見なされるため、理にかなっています。行内データに関する詳細については、以下の記事をご覧ください。

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

概要:AFTERトリガーでは、削除が完了した後にトランザクションログをスキャンバックする必要があります。その後、トランザクションログと時間をさらに使用する必要があるテーブルを作成および挿入/削除する必要があります。

したがって、アクションプランとして、これが現時点で提案されているものです。

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.

2

計画によれば、すべてが正しく進行しています。別のプランを提供するINではなくJOINとして削除を記述してみてください。

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

しかし、それがどれだけ役立つかはわかりません。テーブルのトリガーで削除が実行されているとき、削除を実行するセッションの待機タイプは何ですか?

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.