데이터가 변경되지 않는 업데이트 성능


31

UPDATE실제로 데이터를 변경하지 않는 명령문 이있는 경우 (데이터가 이미 업데이트 된 상태이므로). WHERE업데이트를 방지하기 위해 조항을 확인하면 성능상의 이점이 있습니까?

예를 들어 다음에서 UPDATE 1과 UPDATE 2간에 실행 속도에 차이가있을 수 있습니다.

CREATE TABLE MyTable (ID int PRIMARY KEY, Value int);
INSERT INTO MyTable (ID, Value)
VALUES
    (1, 1),
    (2, 2),
    (3, 3);

-- UPDATE 1
UPDATE MyTable
SET
    Value = 2
WHERE
    ID = 2
    AND Value <> 2;
SELECT @@ROWCOUNT;

-- UPDATE 2
UPDATE MyTable
SET
    Value = 2
WHERE
    ID = 2;
SELECT @@ROWCOUNT;

DROP TABLE MyTable;

내가 묻는 이유는 변경되지 않은 행을 포함하기 위해 행 수가 필요하기 때문에 ID가 없으면 삽입을 수행할지 여부를 알 수 있기 때문입니다. 따라서 UPDATE 2 양식을 사용했습니다. UPDATE 1 양식을 사용하여 성능 이점이 있다면 어떻게 든 필요한 행 수를 얻을 수 있습니까?


sqlperformance.com/2012/10/t-sql-queries/conditional-updates를 참조하십시오 (값이 변경되지 않는 경우를 프로파일 링하지는 않았지만).
Aaron Bertrand

답변:


24

데이터가 실제로 업데이트 된 상태이므로 실제로 데이터를 변경하지 않는 UPDATE 문이있는 경우 업데이트를 방지하기 위해 where 절을 확인하면 성능상의 이점이 있습니까?

UPDATE 1 로 인해 약간의 성능 차이가있을 수 있습니다 .

  • 실제로 행을 업데이트하지 않음 (따라서 디스크에 기록 할 것이 없으며 최소한의 로그 작업조차하지 않음)
  • 실제 업데이트를 수행하는 데 필요한 것보다 덜 제한적인 잠금을 수행하므로 동시성에 더 좋습니다 ( 끝 부분의 업데이트 섹션 참조 )

그러나 스키마, 데이터 및 시스템로드를 사용하여 시스템에서 차이의 정도를 측정해야합니다. 비 업데이트 UPDATE가 미치는 영향은 다음과 같습니다.

  • 업데이트중인 테이블의 경합 양
  • 갱신되고있는 행수
  • 업데이트중인 테이블에 UPDATE 트리거가있는 경우 (질문에 대한 설명에서 Mark로 표시) 을 실행 UPDATE TableName SET Field1 = Field1하면 업데이트 트리거가 실행 되고 필드가 업데이트되었음을 ​​표시하고 ( UPDATE () 또는 COLUMNS_UPDATED 함수를 사용하여 확인한 경우 ) 테이블 INSERTEDDELETED테이블 의 필드 가 동일한 값 임을 나타냅니다 .

또한 다음 요약 섹션은 Paul White의 기사, 비 업데이트 업데이트의 영향 (@spaghettidba가 그의 답변에 대한 주석에서 언급 한대로)에 있습니다.

SQL Server에는 UPDATE 작업을 처리 할 때 영구 데이터베이스를 변경하지 않는 불필요한 로깅 또는 페이지 플러시를 피하기위한 여러 가지 최적화 기능이 포함되어 있습니다.

  • 클러스터 테이블에 대한 비 업데이트 업데이트는 일반적으로 클러스터 키를 구성하는 열이 업데이트 작업의 영향을받지 않는 한 추가 로깅 및 페이지 플러시를 피합니다.
  • 클러스터 키의 일부가 동일한 값으로 '업데이트'되면 작업이 데이터가 변경된 것처럼 기록되고 영향을받는 페이지가 버퍼 풀에서 더티로 표시됩니다. 이것은 UPDATE가 delete-then-insert 조작으로 변환 된 결과입니다.
  • 힙 테이블은 추가 로깅 또는 페이지 플러시를 유발하는 클러스터 키가 없다는 점을 제외하면 클러스터 테이블과 동일하게 작동합니다. 클러스터되지 않은 기본 키가 힙에 존재하는 경우에도 마찬가지입니다. 따라서 힙에 대한 비 업데이트 업데이트는 일반적으로 추가 로깅 및 플러시를 피합니다 (하지만 아래 참조).
  • 힙 및 클러스터 된 테이블 모두 8000 바이트가 넘는 데이터를 포함하는 LOB 열이 'SET column_name = column_name'이외의 구문을 사용하여 동일한 값으로 업데이트되는 행에 대해 추가 로깅 및 플러시가 발생합니다.
  • 데이터베이스에서 두 가지 유형의 행 버전 관리 격리 수준을 사용하면 항상 추가 로깅 및 플러시가 발생합니다. 업데이트 트랜잭션에 적용되는 격리 수준에 관계없이 발생합니다.

다음 두 가지 항목을 염두에 두십시오 (특히 Paul의 전체 기사를보기 위해 링크를 따르지 않는 경우).

  1. 비 업데이트 업데이트에는 여전히 일부 로그 활동이있어 트랜잭션이 시작되고 종료되고 있음을 보여줍니다. 데이터 수정이 발생하지 않는다는 것만으로도 여전히 비용을 절감 할 수 있습니다.

  2. 위에서 언급했듯이 시스템을 테스트해야합니다. Paul이 사용하는 것과 동일한 연구 쿼리를 사용하고 동일한 결과를 얻었는지 확인하십시오. 내 시스템에서 기사에 표시된 것과 약간 다른 결과가 나타납니다. 여전히 더러운 페이지를 작성하지는 않지만 약간의 로그 작업이 필요합니다.


... 변경되지 않은 행을 포함하려면 행 수가 필요하므로 ID가 없으면 삽입을 수행할지 여부를 알 수 있습니다. ... 어떻게 필요한 행 수를 얻을 수 있습니까?

간단히 말해서, 단일 행을 처리하는 경우 다음을 수행 할 수 있습니다.

UPDATE MyTable
SET    Value = 2
WHERE  ID = 2
AND Value <> 2;

IF (@@ROWCOUNT = 0)
BEGIN
  IF (NOT EXISTS(
                 SELECT *
                 FROM   MyTable
                 WHERE  ID = 2 -- or Value = 2 depending on the scenario
                )
     )
  BEGIN
     INSERT INTO MyTable (ID, Value) -- or leave out ID if it is an IDENTITY
     VALUES (2, 2);
  END;
END;

여러 행의 경우 OUTPUT절 을 사용하여 해당 결정을 내리는 데 필요한 정보를 얻을 수 있습니다 . 업데이트 된 행을 정확하게 캡처하면 항목을 좁혀 존재하지 않는 행을 업데이트하지 않고 업데이트가 필요하지 않은 행이 아닌 업데이트되지 않은 행의 차이를 알 수 있습니다.

다음 답변에서 기본 구현을 보여줍니다.

xml 매개 변수를 사용하여 여러 데이터를 업데이트 할 때 병합 쿼리를 사용하지 않는 방법은 무엇입니까?

해당 답변에 표시된 방법은 존재하지만 아직 업데이트 할 필요가없는 행을 필터링하지 않습니다. 해당 부분을 추가 할 수 있지만 먼저 병합 할 데이터 세트를 가져 오는 위치를 정확하게 표시해야합니다 MyTable. 그들은 임시 테이블에서오고 있습니까? TVP (테이블 반환 매개 변수)?


업데이트 1 :

마침내 몇 가지 테스트를 수행 할 수 있었으며 트랜잭션 로그 및 잠금과 관련하여 내가 찾은 것이 있습니다. 먼저 테이블의 스키마 :

CREATE TABLE [dbo].[Test]
(
  [ID] [int] NOT NULL CONSTRAINT [PK_Test] PRIMARY KEY CLUSTERED,
  [StringField] [varchar](500) NULL
);

다음으로 테스트는 필드를 이미 가지고있는 값으로 업데이트합니다.

UPDATE rt
SET    rt.StringField = '04CF508B-B78E-4264-B9EE-E87DC4AD237A'
FROM   dbo.Test rt
WHERE  rt.ID = 4082117

결과 :

-- Transaction Log (2 entries):
Operation
----------------------------
LOP_BEGIN_XACT
LOP_COMMIT_XACT


-- SQL Profiler (3 Lock:Acquired events):
Mode            Type
--------------------------------------
8 - IX          5 - OBJECT
8 - IX          6 - PAGE
5 - X           7 - KEY

마지막으로, 값이 변경되지 않아 업데이트를 필터링하는 테스트 :

UPDATE rt
SET    rt.StringField = '04CF508B-B78E-4264-B9EE-E87DC4AD237A'
FROM   dbo.Test rt
WHERE  rt.ID = 4082117
AND    rt.StringField <> '04CF508B-B78E-4264-B9EE-E87DC4AD237A';

결과 :

-- Transaction Log (0 entries):
Operation
----------------------------


-- SQL Profiler (3 Lock:Acquired events):
Mode            Type
--------------------------------------
8 - IX          5 - OBJECT
7 - IU          6 - PAGE
4 - U           7 - KEY

보시다시피, 트랜잭션의 시작과 끝을 표시하는 두 항목과 달리 행을 필터링 할 때 트랜잭션 로그에 아무것도 기록되지 않습니다. 그리고이 두 항목이 거의 아무것도 아니라는 것이 사실이지만, 그들은 여전히 ​​무언가입니다.

또한 PAGE 및 KEY 리소스의 잠금은 변경되지 않은 행을 필터링 할 때 덜 제한적입니다. 다른 프로세스가이 테이블과 상호 작용하지 않으면 문제가 아닐 수 있습니다 (그러나 실제로는 그 가능성이 얼마나됩니까?). 링크 된 블로그 (및 내 테스트)에 표시된 테스트는 테스트의 일부가 아니기 때문에 테이블에 경합이없는 것으로 암시 적으로 가정합니다. 업데이트가 아닌 업데이트는 너무 가벼워서 필터링을 수행하는 데 비용이 들지 않는다고 말하면 테스트는 진공 상태에서 어느 정도 수행되었으므로 소금 한 알로 채취해야합니다. 그러나 프로덕션 환경에서이 테이블은 격리되지 않을 가능성이 높습니다. 물론, 약간의 로깅과 제한적인 잠금이 효율성을 떨어 뜨리지 않을 수 있습니다. 그렇다면이 질문에 대한 가장 신뢰할만한 정보 출처는 무엇입니까? SQL Server. 구체적으로 :당신의 SQL 서버. 시스템에 어떤 방법이 더 좋은지 보여줍니다 :-).


업데이트 2 :

새 값이 현재 값과 동일한 작업 (즉, 업데이트 없음)과 새 값이 다른 작업의 수를 지정하고 업데이트가 필요한 경우 특히 다음과 같은 패턴이 더 나은 것으로 판명 될 수 있습니다. 테이블에 많은 논쟁이 있습니다. 아이디어는 SELECT현재 값을 얻기 위해 간단한 작업을 먼저하는 것입니다. 값을 얻지 못하면에 대한 답변이 있습니다 INSERT. 가치가 있다면 간단하게 IF하고 필요한 경우 UPDATE 에만 발행 할 수 있습니다 .

DECLARE @CurrentValue VARCHAR(500) = NULL,
        @NewValue VARCHAR(500) = '04CF508B-B78E-4264-B9EE-E87DC4AD237A',
        @ID INT = 4082117;

SELECT @CurrentValue = rt.StringField
FROM   dbo.Test rt
WHERE  rt.ID = @ID;

IF (@CurrentValue IS NULL) -- if NULL is valid, use @@ROWCOUNT = 0
BEGIN
  -- row does not exist
  INSERT INTO dbo.Test (ID, StringField)
  VALUES (@ID, @NewValue);
END;
ELSE
BEGIN
  -- row exists, so check value to see if it is different
  IF (@CurrentValue <> @NewValue)
  BEGIN
    -- value is different, so do the update
    UPDATE rt
    SET    rt.StringField = @NewValue
    FROM   dbo.Test rt
    WHERE  rt.ID = @ID;
  END;
END;

결과 :

-- Transaction Log (0 entries):
Operation
----------------------------


-- SQL Profiler (2 Lock:Acquired events):
Mode            Type
--------------------------------------
6 - IS          5 - OBJECT
6 - IS          6 - PAGE

따라서 3 대신에 2 개의 잠금 만 획득되었으며이 잠금은 모두 Intent eXclusive 또는 Intent Update ( Lock Compatibility )가 아닌 Intent Shared 입니다. 획득 한 각 잠금도 해제되므로 각 잠금은 실제로 2 개의 작업이므로이 새로운 방법은 원래 제안 된 방법의 6 개 작업 대신 총 4 개의 작업입니다. 이 작업이 15ms마다 한 번 (OP에 명시된대로 대략) 실행되는 것을 고려하면 초당 약 66 회입니다. 따라서 원래 제안은 초당 396 개의 잠금 / 잠금 해제 작업에 해당하는 반면,이 새로운 방법은 더 가벼운 잠금의 초당 264 개의 잠금 / 잠금 해제 작업에 해당합니다. 이것은 훌륭한 성능을 보장하는 것은 아니지만 테스트할만한 가치가 있습니다 :-).


14

조금 축소하고 더 ​​큰 그림을 생각하십시오. 현실에서는 업데이트 진술이 실제로 다음과 같이 보일 것입니다.

UPDATE MyTable
  SET Value = 2
WHERE
     ID = 2
     AND Value <> 2;

아니면 다음과 같이 보일까요?

UPDATE Customers
  SET AddressLine1 = '123 Main St',
      AddressLine2 = 'Apt 24',
      City = 'Chicago',
      State = 'IL',
      (and a couple dozen more fields)
WHERE
     ID = 2
     AND (AddressLine1 <> '123 Main St'
     OR AddressLine2 <> 'Apt 24'
     OR City <> 'Chicago'
     OR State <> 'IL'
      (and a couple dozen more fields))

실제로는 테이블에 많은 열이 있기 때문입니다. 즉, 동적 문자열을 작성하려면 많은 복잡한 동적 앱 로직을 생성해야하거나 매번 모든 필드의 전후 내용을 지정해야합니다.

모든 테이블에 대해 이러한 업데이트 명령문을 동적으로 빌드하고 업데이트중인 필드 만 전달 하면 몇 년 전부터 NHibernate 매개 변수 크기 문제 와 유사한 계획 캐시 오염 문제를 빠르게 실행할 수 있습니다 . 더 나쁜 것은 저장 프로 시저와 같이 SQL Server에서 업데이트 문을 작성하면 SQL Server가 문자열을 대규모로 연결하는 데별로 효율적이지 않기 때문에 귀중한 CPU주기를 소모하게됩니다.

이러한 복잡성 때문에 일반적으로 업데이트를 수행 할 때 이러한 종류의 행별, 필드 별 비교를 수행하는 것은 적합하지 않습니다. 대신 세트 기반 작업을 생각하십시오.


1
내 실제 사례는 그렇게 간단하지만 많이 부릅니다. 내 예상은 피크 시간에 15ms마다 한 번씩입니다. SQL Server가 디스크에 쓸 필요가 없을 때 디스크에 쓸 수 없을 정도로 똑똑한 지 궁금합니다.
Martin Brown

3

행 수가 클 때만 업데이트 할 필요가없는 행을 건너 뛰면 성능이 향상되는 것을 볼 수 있습니다 (로깅이 적고 디스크에 쓸 페이지가 덜 더러운 경우).

귀하의 경우와 같이 단일 행 업데이트를 처리 할 때 성능 차이는 거의 무시할 수 있습니다. 모든 경우에 행을 업데이트하는 것이 더 쉬워지면 그렇게하십시오.

이 주제에 대한 자세한 내용은 Paul White의 비 업데이트 업데이트 를 참조하십시오.



1

모든 필드의 값을 확인하는 대신 관심있는 열을 사용하여 해시 값을 얻을 수 없으며 테이블의 행에 저장된 해시와 비교할 수 있습니까?

IF EXISTS (Select 1 from Table where ID =@ID AND HashValue=Sha256(column1+column2))
GOTO EXIT
ELSE
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.