이 while 루프에 명시 적 트랜잭션이 필요합니까?


11

SQL Server 2014 :

우리는 매우 큰 (1 억 행) 테이블을 가지고 있으며 테이블에 몇 개의 필드를 업데이트해야합니다.

로그 배송 등을 위해, 우리는 또한 한입 규모의 거래를 유지하기를 원합니다.

아래를 약간 실행 한 다음 쿼리를 취소 / 종료하면 지금까지 수행 한 작업이 모두 커밋되거나 언제라도 취소 할 수 있도록 명시적인 BEGIN TRANSACTION / END TRANSACTION 문을 추가해야합니까?

DECLARE @CHUNK_SIZE int
SET @CHUNK_SIZE = 10000

UPDATE TOP(@CHUNK_SIZE) [huge-table] set deleted = 0, deletedDate = '2000-01-01'
where deleted is null or deletedDate is null

WHILE @@ROWCOUNT > 0
BEGIN
    UPDATE TOP(@CHUNK_SIZE) [huge-table] set deleted = 0, deletedDate = '2000-01-01'
    where deleted is null or deletedDate is null
END

답변:


13

개별 명세서 (DML, DDL 등)는 거래 자체입니다. 예, 루프가 반복 될 때마다 (기술적으로는 각 명령문 이후) 해당 UPDATE명령문이 변경 되면 자동 커밋됩니다.

물론 예외는 항상 있습니다. 을 통해 암시 적 트랜잭션 수 있도록 할 수 있습니다 SET IMPLICIT_TRANSACTIONS 첫 번째 경우에, UPDATE문은 당신이해야하는 트랜잭션을 시작할 것 COMMIT또는 ROLLBACK말을. 대부분의 경우 기본적으로 꺼져있는 세션 수준 설정입니다.

언제라도 취소 할 수 있도록 명시적인 BEGIN TRANSACTION / END TRANSACTION 문을 추가해야합니까?

실제로 프로세스를 중지했다가 다시 시작하려는 경우 프로세스를 중지하기 전에 프로세스를 중지 할 수 있으므로 명시 적 트랜잭션을 추가하거나 암시 적 트랜잭션을 활성화하는 것은 좋지 않습니다 COMMIT. 이 경우 COMMITSSMS에있는 경우 수동으로 실행하거나 SQL 에이전트 작업에서이 작업을 실행하는 경우 해당 기회가 없어 고아 트랜잭션이 발생할 수 있습니다.


또한 @CHUNK_SIZE더 작은 숫자 로 설정할 수 있습니다. 잠금 에스컬레이션은 일반적으로 단일 오브젝트에서 획득 한 5000 개의 잠금에서 발생합니다. 행 크기와 행 잠금 대 페이지 잠금을 수행하는 경우 해당 한계를 초과 할 수 있습니다. 행의 크기가 각 페이지 당 1 또는 2 개의 행만 들어가는 크기이면 페이지 잠금을 수행하더라도 항상이 오류가 발생할 수 있습니다.

테이블이 분할 된 경우 테이블을 에스컬레이션 할 때 전체 테이블이 아닌 파티션 만 잠그도록 LOCK_ESCALATION테이블에 대한 옵션 (SQL Server 2008에 도입) 을 설정하는 옵션이 AUTO있습니다. 또는 모든 테이블에 대해 동일한 옵션을로 설정할 수 DISABLE있지만 매우주의해야합니다. 자세한 내용은 ALTER TABLE 을 참조하십시오.

다음은 잠금 에스컬레이션 및 임계 값에 대해 설명하는 문서입니다. 잠금 에스컬레이션 ( "SQL Server 2008 R2 이상 버전"에 적용됨). 다음은 잠금 에스컬레이션 감지 및 수정을 다루는 블로그 게시물입니다. Microsoft SQL Server에서 잠금 (12 부 – 잠금 에스컬레이션) .


정확한 질문과 관련이 없지만 질문의 쿼리와 관련하여 여기에서 수행 할 수있는 몇 가지 개선 사항이 있습니다 (적어도 그것을 보는 것만으로도 그렇게 보입니다).

  1. WHILE (@@ROWCOUNT = @CHUNK_SIZE)마지막 반복에서 업데이트 된 행 수가 UPDATE 요청 된 양보다 적 으면 수행 할 작업이 없으므로 루프의 경우 약간 더 좋습니다.

  2. 경우] deleted필드는 인 BIT데이터 유형은 다음의 여부에 의해 값을 결정하는 것이 아니다 deletedDate이다 2000-01-01? 왜 둘 다 필요합니까?

  3. 이 두 필드가 새 필드이고 NULL온라인 / 비 차단 작업 일 수 있고 추가하여 "기본"값으로 업데이트하려는 경우에는 필요하지 않았습니다. SQL Server 2012 (Enterprise Edition 만 해당)부터 NOT NULLDEFAULT 제약 조건이있는 열을 추가 하면 DEFAULT의 값이 상수 인 한 블로킹 작업이 아닙니다. 따라서 필드를 아직 사용하지 않는 경우 NOT NULLDEFAULT 제약 조건을 사용하여 삭제하고 다시 추가하십시오 .

  4. 이 업데이트를 수행하는 동안 다른 프로세스가이 필드를 업데이트하지 않는 경우 업데이트하려는 레코드를 큐에 넣은 다음 해당 큐에서 작업하면 더 빠릅니다. 변경해야하는 세트를 얻기 위해 매번 테이블을 다시 쿼리해야하므로 현재 메소드에서 성능이 저하되었습니다. 대신 다음 두 가지 필드에서 한 번만 테이블을 스캔 한 다음 매우 목표가 지정된 UPDATE 문만 발행하는 다음을 수행 할 수 있습니다. 대기열의 초기 채우기는 단순히 업데이트 할 레코드를 찾기 때문에 언제든지 프로세스를 중지했다가 나중에 시작하면 위약금이 없습니다.

    1. 클러스터형 인덱스의 키 필드 만있는 임시 테이블 (#FullSet)을 만듭니다.
    2. 동일한 구조의 두 번째 임시 테이블 (#CurrentSet)을 만듭니다.
    3. 통해 #FullSet에 삽입 SELECT TOP(n) KeyField1, KeyField2 FROM [huge-table] where deleted is null or deletedDate is null;

      TOP(n)때문에 테이블의 크기에 거기에있다. 테이블에 1 억 개의 행이 있으면 특히 프로세스를 자주 중지하고 나중에 다시 시작하려는 경우 큐 테이블을 전체 키 세트로 채울 필요가 없습니다. 아마도 n백만으로 설정 되어 완료 될 수 있습니다. 백만 세트 (또는 그 이하)를 실행하는 SQL 에이전트 작업에서 항상이를 예약 한 후 다음 예약 시간이 다시 걸릴 때까지 기다릴 수 있습니다. 그런 다음 20 분마다 실행하도록 스케줄을 설정하여 세트 사이에 강제 호흡 공간이 n있지만 여전히 전체 프로세스를 무인으로 완료합니다. 그런 다음 더 이상 할 일이 없으면 작업을 삭제하십시오. :-).

    4. 루프에서 다음을 수행하십시오.
      1. 다음과 같은 것을 통해 현재 배치를 채 웁니다. DELETE TOP (4995) FROM #FullSet OUTPUT Deleted.KeyField INTO #CurrentSet (KeyField);
      2. IF (@@ROWCOUNT = 0) BREAK;
      3. 다음과 같은 것을 사용하여 업데이트를 수행하십시오. UPDATE ht SET ht.deleted = 0, ht.deletedDate='2000-01-01' FROM [huge-table] ht INNER JOIN #CurrentSet cs ON cs.KeyField = ht.KeyField;
      4. 현재 세트를 지우십시오. TRUNCATE TABLE #CurrentSet;
  5. 경우에 따라 필터링 된 인덱스를 추가 SELECT하여 #FullSet임시 테이블 로의 피드 를 지원하는 데 도움이 됩니다. 이러한 인덱스 추가와 관련된 몇 가지 고려 사항은 다음과 같습니다.
    1. WHERE 조건은 쿼리의 WHERE 조건과 일치해야합니다. WHERE deleted is null or deletedDate is null
    2. 프로세스가 시작될 때 대부분의 행은 WHERE 조건과 일치하므로 인덱스가 도움이되지 않습니다. 이것을 추가하기 전에 50 % 정도가 될 때까지 기다릴 수 있습니다. 물론, 인덱스를 추가하는 것이 얼마나 도움이되고 언제 가장 좋은지 몇 가지 요인에 따라 달라 지므로 시행 착오가 조금 발생합니다.
    3. 기본 데이터가 자주 변경되기 때문에 인덱스를 수동으로 업데이트 및 / 또는 인덱스를 최신 상태로 유지해야 할 수도 있습니다.
    4. 을 돕는 동안 인덱스 는 해당 작업 중에 업데이트해야하는 또 다른 개체이므로 더 많은 I / O를 수행하므로 인덱스 SELECT가 손상 될 수 있음을 명심하십시오 UPDATE. 이것은 필터링 된 인덱스 (필터와 일치하는 행이 적기 때문에 행을 업데이트함에 따라 줄어 듭니다)와 인덱스를 추가하기 위해 잠시 기다립니다 (처음에 큰 도움이되지 않는 경우 발생하지 않을 이유가 없음) 추가 I / O).

업데이트 : 상태를 추적하고 깨끗하게 취소하는 메커니즘을 포함하여 위에서 제안 된 모든 기능을 구현하려면이 질문과 관련된 질문에 대한 답변을 참조하십시오 .SQL 서버 : 작은 덩어리로 거대한 테이블의 필드 업데이트 : 얻는 방법 진행 / 상태?


# 4의 제안은 경우에 따라 더 빠를 수도 있지만 추가하기에는 코드가 상당히 복잡해 보입니다. 나는 간단하게 시작하는 것이 좋으며, 그것이 당신의 요구를 충족시키지 못하면 대안을 고려하십시오.
베이컨 비트

@BaconBits 간단한 시작에 동의합니다. 공평하게, 이러한 제안들이 모든 시나리오에 적용되는 것은 아닙니다. 문제는 매우 큰 (1 억 + 행) 테이블을 다루는 것입니다.
Solomon Rutzky 2016 년
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.