트리거가 활성화 된 경우 레코드의 느린 삭제


17

이것은 아래 링크-해결 방법-으로 해결되었지만 패치는 그렇지 않습니다. Microsoft 지원 부서와 협력하여 해결합니다.

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

좋아, 누군가 누군가 아이디어가 있는지 확인하기 위해 StackOverflow에 버리고 싶었던 문제가 있습니다.

이것은 SQL Server 2008 R2와 관련이 있습니다.

문제 : 15000 개의 레코드가있는 테이블에서 3000 개의 레코드를 삭제하면 트리거가 활성화 된 경우 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 %)

트리거

테이블에는 3 개의 트리거가 있습니다 (삽입, 업데이트 및 삭제 작업에 각각 하나씩). 삭제 트리거의 코드를 수정하여 리턴 한 다음 실행 횟수를 확인하기 위해 하나를 선택했습니다. 예상대로 전체 작업 중에 한 번만 발생합니다.

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트리거 ( inserteddeleted의사 테이블 생성을 용이하게하기 위해 ), 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 바이트).

업데이트 예

DBCC PAGERecord Attributes = NULL_BITMAP VERSIONING_INFO Record Size = 71표의 다른 모든 행에있는 반면 업데이트 된 단일 행에는가 있음을 나타냅니다 Record Attributes = NULL_BITMAP; record Size = 57.

동일한 스크립트 UPDATE가 단일 행으로 바뀌면 다음과 같은 DELETE출력이 생성됩니다.

DELETE dbo.Example
WHERE ID = 1;

예제 삭제

총 하나의 행은 물론 (물론!) 최대 물리적 행 크기는 증가하지 않았습니다. 행 버전 관리 정보는 트리거 의사 테이블에 필요한 행에만 추가되며 해당 행은 결국 삭제되었습니다. 그러나 페이지 분할은 유지됩니다. 이 페이지 분할 작업은 트리거가있을 때 관찰 된 성능 저하를 담당합니다. 의 정의 경우 Padding2열이 변경됩니다 varchar(8000)varchar(7999)더 이상 분할 페이지.

또한 SQL Server MVP Dmitri Korotkevitch 의이 블로그 게시물 을 참조하십시오 . 여기에는 조각화에 대한 영향도 설명되어 있습니다.


1
아, 나는 이것에 대해 언젠가 SO에 대해 질문 했지만 결정적인 대답을 얻지 못했습니다.
Martin Smith

5

Microsoft의 공식 답변은 다음과 같습니다. 주요 디자인 결함이라고 생각합니다.

2011 년 11 월 14 일-공식 답변이 변경되었습니다. 앞에서 설명한대로 트랜잭션 로그를 사용하지 않습니다. 내부 저장소 (행 레벨)를 사용하여 변경된 데이터를 복사합니다. 그들은 왜 그렇게 오래 걸렸는지를 여전히 판단 할 수 없습니다.

after delete 트리거 대신 대신 Of Of 트리거를 사용하기로 결정했습니다.

트리거의 AFTER 부분으로 인해 삭제가 완료된 후 트랜잭션 로그를 읽고 트리거 삽입 / 삭제 테이블을 작성해야합니다. 이곳은 우리가 많은 시간을 소비하는 곳이며 방아쇠 후 부분적으로 설계되었습니다. INSTEAD OF 트리거는 트랜잭션 로그를 스캔하고 삽입 / 삭제 된 테이블을 작성하는 이러한 동작을 방지합니다. 또한 nvarchar (max)로 모든 열을 삭제하면 LOB 데이터로 간주되므로 의미가 훨씬 빠릅니다. In-Row 데이터에 대한 자세한 정보는 아래 기사를 참조하십시오.

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.