트리거에서 INSERTED 및 DELETED 테이블을 조인하는 끔찍한 성능


12

특정 열이 특정 값에서 다른 값으로 변경되는 것을 감시하는 테이블에 UPDATE 트리거가 있습니다. 이 경우 단일 UPDATE 문을 통해 다른 테이블의 일부 관련 데이터를 업데이트합니다.

트리거가 수행하는 첫 번째 작업은 업데이트 된 행에이 열의 값이 해당 값에서 변경되었는지 확인하는 것입니다. INSERTED를 DELETED에 조인하고 해당 열의 값을 비교합니다. 자격이 없으면 UPDATE 문이 실행되지 않도록 조기에 구제됩니다.

IF NOT EXISTS (
    SELECT TOP 1 i.CUSTNMBR
    FROM INSERTED i
        INNER JOIN DELETED d
            ON i.CUSTNMBR = d.CUSTNMBR
    WHERE d.CUSTCLAS = 'Misc'
        AND i.CUSTCLAS != 'Misc'
)
    RETURN

이 경우 CUSTNMBR은 기본 테이블의 기본 키입니다. 이 테이블에서 큰 업데이트 (예 : 5000+ 행)를 수행하는 경우 CUSTCLAS 열을 건드리지 않아도이 명령문은 AGES를 사용합니다. Profiler에서 몇 분 동안이 진술에서 멈추는 것을 볼 수 있습니다.

실행 계획은 기괴합니다. 3,714 개의 실행과 ~ 1,850 만 개의 출력 행을 가진 삽입 된 스캔을 보여줍니다. CUSTCLAS 열의 필터를 통해 실행됩니다. 이를 (중첩 루프를 통해) 삭제 된 스캔 (CUSTCLAS에서도 필터링 됨)에 결합합니다.이 스캔은 한 번만 실행되며 5000 개의 출력 행이 있습니다.

이 문제를 일으키기 위해 내가 무슨 바보 같은 일을하고 있습니까? 트리거는 여러 행 업데이트를 올바르게 처리해야합니다.

편집 :

나는 또한 이것을 EXISTS가 불쾌한 일을하는 경우와 같이 작성하려고 시도했지만 여전히 끔찍합니다.

DECLARE @CUSTNMBR varchar(31)
SELECT TOP 1 @CUSTNMBR = i.CUSTNMBR
FROM INSERTED i
    INNER JOIN DELETED d
        ON i.CUSTNMBR = d.CUSTNMBR
WHERE d.CUSTCLAS = 'Misc'
    AND i.CUSTCLAS != 'Misc'

IF @CUSTNMBR IS NULL
    RETURN

"TOP 1"을 제거 할 수 있습니까? 나는 그것이 단지 하나의 사례가 있는지 확인하는 경우 필요하지 않을 수도있는 약간의 오버 헤드를 야기한다고 생각할 것이다.
JHFB

답변:


10

당신은 명시 적으로 사용하여 평가할 수있다 INNER MERGE JOIN또는 INNER HASH JOIN당신은 아마도 나중에 다시 당신은 아마 더 나은 오프 단지의 내용을 삽입하는 트리거에서이 테이블을 사용하고 있는지 힌트 만 제공을 inserted하고 deleted인덱스로 테이블을 #temp테이블과 함께 수행되고.

유용한 인덱스는 자동으로 생성되지 않습니다.


자, 이렇게하면 속도는 엄청나게 빨라지지만 계단식 트리거 실행 가능성이 있습니다. 각 트리거에 동일한 임시 테이블 이름 (#i, #d)을 사용하면 충돌합니다. 모든 트리거에 다른 임시 테이블 이름을 사용하는 것보다 더 나은 / 안전한 솔루션이 있습니까?
db2

CUSTNMBR고유 한 클러스터형 인덱스를 만들기 위해 기본 키가 정의 된 테이블 변수를 사용하여 평가 하고 OPTION (RECOMPILE)힌트를 사용하여 행 수를 고려하거나 다음과 같은 특정 명명 규칙을 사용할 수 있습니다.#i_dbo_YourTable
Martin Smith

나는 그것들의 이름을 다음과 같이 정할 것이라고 생각합니다 #trigger_name_i. 테이블 변수를 사용하면 명시 적 CREATE TABLEs로 코드를 더 복잡하게 만들어야합니다. 계단식 트리거가 있지만 재귀 트리거는 없으므로 안전하다고 생각합니다.
db2

이 목적을 위해 임시 테이블 대신 테이블 변수를 권장합니다. 테이블 변수는 여전히 1 차 및 2 차 (고유) 인덱스를 가질 수 있으며, 트리거가 종료되고 테이블 변수가 해당 트리거 실행으로 범위가 지정 될 때 자동으로 정리되며 테이블 이름은 동일한 이름의 다른 테이블 변수와 충돌하지 않습니다. 호출 스택). 테이블 정의 코드 오버 헤드를 절약하려면 각각에 대해 테이블 ​​유형을 정의하고 유형 이름을 사용하여 테이블 변수를 선언하십시오.
Chris Smith

@ChrisSmith 당신은 또한 OPTION (RECOMPILE)카디널리티가 고려되도록 종종 필요할 것 입니다.
Martin Smith

10

나는 이것이 답변되었지만 최근에 활성화 된 것으로 나타 났으며 수백만 행의 테이블에 대해서도 마찬가지입니다. 허용 된 답변을 할인하지는 않지만 비슷한 경험을 수행 할 때 트리거 성능의 주요 요소 (하나 이상의 열에 실제로 값이 변경되었는지 확인)가 열인지 여부를 경험에 추가 할 수 있습니다. 테스트를받는 것은 실제로 UPDATE성명서의 일부였습니다 . 실제로 명령문의 일부가 아닌 테이블 inserteddeleted테이블 사이의 열을 비교 하면 해당 필드가 필드의 일부인 경우에는 그렇지 않은 성능에 큰 끌림이 있음을 알았 습니다.UPDATEUPDATE진술 (실제로 가치가 변경되는 것과 상관없이). 해당 열이 변경 될 가능성을 논리적으로 배제 할 수있는 경우 어떤 항목이 변경되었는지 판별하기 위해 모든 작업 (예 : X 행에서 N 필드를 비교하는 조회)을 수행하는 이유는 무엇입니까? 진술 의 SET절에서 UPDATE.

내가 사용한 솔루션 은 트리거 내부에서만 작동 하는 UPDATE () 함수 를 사용하는 것이 었습니다 . 이 내장 함수는 UPDATE명령문 에 열이 지정되었는지 알려주며, 관심이있는 열이의 일부가 아닌 경우 트리거를 종료하는 데 사용할 수 있습니다 UPDATE. 이를 SELECT열과 함께 사용 한다고 가정하고 해당 열에 UPDATE실제 변경 사항이 있는지 확인하기 위해 a와 함께 사용할 수 있습니다 . 다음과 같은 여러 감사 트리거의 맨 위에 코드가 있습니다.

-- exit on updates that do not update the only 3 columns we ETL
IF (
     EXISTS(SELECT 1 FROM DELETED) -- this is an UPDATE (Trigger is AFTER INSERT, UPDATE)
     AND (
            NOT (UPDATE(Column3) OR UPDATE(Column7)
                 OR UPDATE(Column11)) -- the columns we care about are not being updated
            OR NOT EXISTS(
                        SELECT 1
                        FROM INSERTED ins
                        INNER JOIN DELETED del
                                ON del.KeyField1 = ins.KeyField1
                                AND del.KeyField2 = ins.KeyField2
                        WHERE ins.Column3 <> del.Column3
                                 COLLATE Latin1_General_100_CS_AS -- case-sensitive compare
                        OR    ISNULL(ins.Column7, -99) <> 
                                 ISNULL(del.Column7, -99) -- NULLable INT field
                        OR    ins.[Column11] <> del.[Column11] -- NOT NULL INT field
                      )
          )
    )
BEGIN
    RETURN;
END;

이 논리는 다음과 같은 경우 나머지 트리거로 진행됩니다.

  1. 작업은 INSERT
  2. 관련 필드들 중 적어도 하나는 인 SETUPDATE 하나의 행에 해당 열 중 적어도 하나가 변경된

NOT (UPDATE...) OR NOT EXISTS()이상하게 보이거나 뒤로 보일 수 있지만 관련 열이에 속하지 않으면 SELECTon inserteddeletedtable을 수행하지 않도록 설계되었습니다 UPDATE.

필요에 따라 COLUMNS_UPDATED () 함수는 UPDATE명령문의 일부 열을 판별하는 또 다른 옵션 입니다.


1
그들이 UPDATE(CUSTCLAS)거짓이라면 (+1) 모든 것을 확인 하고 건너 뛰어야 한다는 좋은 지적입니다 . 업데이트되지 않은 열을 행 버전에서 업데이트 된 열만큼 쉽게 사용할 수는 없다고 생각합니다.
Martin Smith

@MartinSmith, 어떻게하면 다른 방법으로 증명할 수 있습니까? 그러나 내가 찾은 방식으로 행동을 예측할 수 있는지 여부는 중요하지 않을 수 있습니다. WHERE의 필드가 UPDATE의 SET에 있는지 여부에 따라 동일한 SELECT를 수행하고 INSERTED와 DELETED 사이에 참여하면서 실제 차이에 대한 필드를 확인하는 것이 엄청난 성능 차이라는 것을 알고 있습니다. 내가 본 행동은 일관성이 있기 때문에 내 이론은 사실이지만 실제 이유를 아는 것이 좋을 것입니다. SET에없는 필드는 값을 위해 기본 테이블로 되돌아 가야한다고 생각했습니다.
Solomon Rutzky

나는 전에 이것의 구조를 보았습니다. 내가 그 일을하는 좋은 방법을 발견하면 내가 기억할 수 없거나 난 그냥 쉽게 할 수 문자열을 통해 철저한 검색을 찾을 사용 tempdbDBCC PAGE
마틴 스미스

확인. 최소 크기의 단일 파일이있는 인스턴스 에서이 스크립트를tempdb 시도 하고 출력을 메모장에 붙여 넣고 "EEEEEE"를 검색했습니다. 여기 스크린 샷 의 출력 이 표시됩니다 . 두 행 모두에서 두 열의 버전 전후에 유의하십시오. 훨씬 쉬운 방법이 있지만 여기서는 내 목적에 충분할 수 있습니다!
Martin Smith

실제로 tempdb페이지 옆에 BBBBBB또는 옆 에 다른 긴 EEEEEE 문자열이 DDDDDD있습니다. 좀 더 조사해야 할 수도 있습니다! 아마도 이것은 REPLICATE전화 때문일 수 있습니다.
Martin Smith

2

존재하는 경우를 사용하여 다시 쓰려고 할 수 있습니다.

IF EXISTS (SELECT TOP 1 i.CUSTNMBR     
            FROM INSERTED i         
            INNER JOIN DELETED d             
            ON i.CUSTNMBR = d.CUSTNMBR and d.custclass = 'Misc'  
            WHERE d.CUSTCLAS <>i.CUSTCLAS)    
BEGIN

--do your triggerstuff here
END

1

http://dave.brittens.org/blog/writing-well-behaved-triggers.html

Dave에 따르면 가상 INSERTED / DELETED 테이블에 인덱스가없는 임시 테이블 또는 테이블 변수를 사용해야합니다. 재귀 트리거가 발생할 수있는 경우 이름 충돌을 피하기 위해 테이블 ​​변수를 사용해야합니다.

누군가가 원래 게시물이 꽤 오래 전에 도움이되기를 바랍니다.


-1

다음 코드는이 트리거의 성능을 향상시킬 수 있습니다. [custclass] 열의 올바른 데이터 유형을 몰랐 으므로 조정해야합니다.

DECLARE @i AS TABLE (CUSTNMBR VARCHAR(31) NOT NULL PRIMARY KEY, custclass VARCHAR(10) NOT NULL)
DECLARE @d AS TABLE (CUSTNMBR VARCHAR(31) NOT NULL PRIMARY KEY, custclass VARCHAR(10) NOT NULL)
INSERT INTO @i SELECT CUSTNMBR, custclass FROM inserted
INSERT INTO @d SELECT CUSTNMBR, custclass FROM deleted
IF NOT EXISTS
  (SELECT * FROM @i AS i INNER JOIN @d AS d ON d.CUSTNMBR = i.CUSTNMBR
   WHERE i.custclass <> d.custclass) RETURN

트리거 코드에 필요한 경우 삽입삭제 된 테이블 의 메모리 사본에 추가 열을 포함시킬 수 있습니다 . 이 테이블의 기본 키는 한 번에 여러 행을 업데이트 할 때 조인 성능을 크게 향상시킵니다. 행운을 빕니다!

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.