SELECT를 차단하는 대규모 INSERT


14

SELECT 작업을 차단하는 대량의 INSERT에 문제가 있습니다.

개요

나는 이와 같은 테이블을 가지고있다 :

CREATE TABLE [InverterData](
    [InverterID] [bigint] NOT NULL,
    [TimeStamp] [datetime] NOT NULL,    
    [ValueA] [decimal](18, 2) NULL,
    [ValueB] [decimal](18, 2) NULL
    CONSTRAINT [PrimaryKey_e149e28f-5754-4229-be01-65fafeebce16] PRIMARY KEY CLUSTERED 
    (
        [TimeStamp] DESC,
        [InverterID] ASC
    ) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF
    , IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON
    , ALLOW_PAGE_LOCKS = ON)
)

또한이 작은 도우미 절차가있어서 MERGE 명령으로 삽입하거나 업데이트 (충돌시 업데이트) 할 수 있습니다.

CREATE PROCEDURE [InsertOrUpdateInverterData]
    @InverterID bigint, @TimeStamp datetime
    , @ValueA decimal(18,2), @ValueB decimal(18,2)
AS
BEGIN
    MERGE [InverterData] AS TARGET
        USING (VALUES (@InverterID, @TimeStamp, @ValueA, @ValueB))
        AS SOURCE ([InverterID], [TimeStamp], [ValueA], [ValueB])
        ON TARGET.[InverterID] = @InverterID AND TARGET.[TimeStamp] = @TimeStamp
    WHEN MATCHED THEN
        UPDATE
        SET [ValueA] = SOURCE.[ValueA], [ValueB] = SOURCE.[ValueB]              
    WHEN NOT MATCHED THEN
        INSERT ([InverterID], [TimeStamp], [ValueA], [ValueB]) 
        VALUES (SOURCE.[InverterID], SOURCE.[TimeStamp], SOURCE.[ValueA], SOURCE.[ValueB]);
END

용법

이제 [InsertOrUpdateInverterData]절차를 빠르게 호출하여 대규모 업데이트를 수행하는 여러 서버에서 서비스 인스턴스를 실행했습니다 .

[InverterData]테이블 에서 SELECT 쿼리를 수행하는 웹 사이트도 있습니다 .

문제

[InverterData]테이블 에서 SELECT 쿼리를 수행하면 서비스 인스턴스의 INSERT 사용에 따라 다른 시간 범위로 진행됩니다. 모든 서비스 인스턴스를 일시 중지하면 SELECT가 매우 빠릅니다. 인스턴스가 빠른 삽입을 수행하면 SELECT가 실제로 느려지거나 시간 초과 취소됩니다.

시도

[sys.dm_tran_locks]잠금 프로세스를 찾기 위해 테이블에서 일부 SELECT를 수행 했습니다.

SELECT
tl.request_session_id,
wt.blocking_session_id,
OBJECT_NAME(p.OBJECT_ID) BlockedObjectName,
h1.TEXT AS RequestingText,
h2.TEXT AS BlockingText,
tl.request_mode

FROM sys.dm_tran_locks AS tl

INNER JOIN sys.dm_os_waiting_tasks AS wt ON tl.lock_owner_address = wt.resource_address
INNER JOIN sys.partitions AS p ON p.hobt_id = tl.resource_associated_entity_id
INNER JOIN sys.dm_exec_connections ec1 ON ec1.session_id = tl.request_session_id
INNER JOIN sys.dm_exec_connections ec2 ON ec2.session_id = wt.blocking_session_id
CROSS APPLY sys.dm_exec_sql_text(ec1.most_recent_sql_handle) AS h1
CROSS APPLY sys.dm_exec_sql_text(ec2.most_recent_sql_handle) AS h2

결과는 다음과 같습니다.

여기에 이미지 설명을 입력하십시오

S = 공유 보류 세션에는 자원에 대한 공유 액세스 권한이 부여됩니다.

질문

[InsertOrUpdateInverterData]MERGE 명령 만 사용 하는 프로 시저에 의해 SELECT가 차단되는 이유는 무엇 입니까?

내부에 정의 된 격리 모드로 어떤 종류의 트랜잭션을 사용해야 [InsertOrUpdateInverterData]합니까?

업데이트 1 (@Paul의 질문 관련)

[InsertOrUpdateInverterData]다음 통계 에 대한 MS-SQL 서버 내부보고를 기반으로 합니다.

  • 평균 CPU 시간 : 0.12ms
  • 평균 읽기 프로세스 : 5.76 / 초
  • 평균 쓰기 프로세스 : 초당 0.4

이것에 기초하여 MERGE 명령은 대부분 테이블을 잠글 수있는 읽기 작업으로 바쁩니다! (?)

업데이트 2 (@Paul의 질문 관련)

[InverterData]테이블은 저장 통계를 다음있다 :

  • 데이터 공간 : 26,901.86MB
  • 행 수 : 131,827,749
  • 분할 : true
  • 파티션 수 : 62

다음은 (가장 완전한) 완전한 sp_WhoIsActive 결과 집합입니다.

SELECT 명령

  • dd hh : mm : ss.mss : 00 00 : 01 : 01.930
  • session_id : 73
  • wait_info : (12629ms) LCK_M_S
  • CPU : 198
  • blocking_session_id : 146
  • 읽는다 : 99,368
  • 씁니다 : 0
  • 상태 : 일시 중지
  • open_tran_count : 0

차단 [InsertOrUpdateInverterData]명령

  • dd hh : mm : ss.mss : 00 00 : 00 : 00.330
  • session_id : 146
  • wait_info : NULL
  • CPU : 3,972
  • blocking_session_id : NULL
  • 읽는다 : 376,95
  • 기록 : 126
  • 상태 : 수면
  • open_tran_count : 1

([TimeStamp] DESC, [InverterID] ASC)외모는 클러스터 된 인덱스의 이상한 선택을 좋아한다. 나는 그 DESC부분을 의미한다 .
ypercubeᵀᴹ

나는 당신의 요점을 이해합니다 : 데이터를 삽입하는 클러스터형 인덱스 DESC는 테이블 재 구축을 강제로 끝내고 결국 성능을 저하시킵니다. 재 구축이 진행되는 동안 테이블을 잠글 것입니다. Jove에 의해 당신은 그것을 가지고 있습니다. 구조는 잠금 장치 이상을 잠그는 원인입니다.
Alocyte

답변:


12

먼저, 주요 질문과 약간 관련이 없지만, 귀하의 MERGE진술은 경쟁 조건 으로 인해 오류가 발생할 위험이 있습니다 . 간단히 말해서, 여러 동시 스레드가 대상 행이 존재하지 않는다고 결론을 내릴 수있어 충돌하는 삽입 시도가 발생합니다. 근본적인 원인은 존재하지 않는 행에서 공유 또는 업데이트 잠금을 수행 할 수 없기 때문입니다. 해결책은 힌트를 추가하는 것입니다.

MERGE [dbo].[InverterData] WITH (SERIALIZABLE) AS [TARGET]

직렬화 격리 수준 힌트 갈 것 행이 잠겨 키 범위를 보장합니다. 범위 잠금을 지원하는 고유 인덱스가 있으므로이 힌트는 잠금에 악영향을 미치지 않으며,이 잠재적 경쟁 조건에 대한 보호를 얻게됩니다.

주요 질문

명령 SELECTs만 사용하는 [InsertOrUpdateInverterData] 프로 시저에 의해 차단 된 이유는 무엇 MERGE입니까?

기본 잠금 읽기 커밋 된 격리 수준에서 데이터를 읽을 때 공유 잠금 (S) 잠금이 사용되며 일반적으로 읽기가 완료된 직후에 항상 해제되는 것은 아닙니다. 일부 공유 잠금은 명령문 끝에 있습니다.

MERGE문 수정 데이터는 S를 취득하거나 할 수 있도록 업데이트 (U) 잠금으로 변환 변화 데이터를 찾을 때 전용 (X) 잠금 단지 실제 수정을 수행하기 전에. 트랜잭션이 끝날 때까지 U 및 X 잠금이 유지되어야합니다.

이는 '최적화' 스냅 샷 격리 (SI)를 제외한 모든 격리 수준에서 적용 되며 RCSI ( 읽기 커밋 된 스냅 샷 격리) 라고도하는 버전 확인 읽기 커밋과 혼동되지 않습니다 .

귀하의 질문에 아무것도 U 잠금을 보유한 세션에 의해 S 잠금이 차단되기를 기다리는 세션이 표시되지 않습니다. 이 잠금 장치는 호환됩니다 . 보류 된 X 잠금을 차단하면 거의 모든 차단이 발생합니다. 짧은 시간 간격으로 많은 수의 단기 잠금을 수행, 변환 및 해제 할 때 캡처하기가 약간 까다로울 수 있습니다.

그만큼 open_tran_count: 1InsertOrUpdateInverterData 명령에는 조사 가치가있다. 명령이 오래 실행되지 않았지만 불필요하게 긴 포함 트랜잭션 (응용 프로그램 또는 상위 수준 저장 프로 시저)이 없는지 확인해야합니다. 가장 좋은 방법은 트랜잭션을 최대한 짧게 유지하는 것입니다. 이것은 아무것도 아닐 수도 있지만 반드시 확인해야합니다.

잠재적 인 해결책

Kin이 의견에서 제안한 것처럼 이 데이터베이스 에서 행 버전 화 격리 수준 (RCSI 또는 SI) 을 활성화 할 수 있습니다. RCSI는 일반적으로 응용 프로그램을 많이 변경하지 않아도되므로 가장 많이 사용됩니다. 활성화되면 기본 읽기 커밋 된 격리 수준은 읽기를 위해 S 잠금을 수행하는 대신 행 버전을 사용하므로 SX 차단이 줄어들거나 제거됩니다. 일부 작업 (예 : 외래 키 검사)은 여전히 ​​RCSI 하에서 S 잠금을 획득합니다.

행 버전은 tempdb 공간을 소비하지만 변경 활동 및 트랜잭션 길이에 비례하여 광범위하게 사용됩니다. 귀하 의 사례에서 RCSI (또는 SI)의 영향을 이해하고 계획하려면 로드 상태 에서 구현을 철저히 테스트해야합니다 .

전체 워크로드에 대해 버전 관리를 사용하지 않고 버전 관리 사용법을 현지화하려는 경우 SI가 여전히 더 나은 선택 일 수 있습니다. 읽기 트랜잭션에 SI를 사용하면 동시 수정이 시작되기 전에 행의 버전을 보는 독자에게 비용이 들지만 독자와 작가 사이의 경합을 피할 수 있습니다 (보다 정확하게는 SI의 읽기 작업은 항상 커밋 된 상태를 볼 수 있음) SI 트랜잭션이 시작된 시점의 행). 쓰기 잠금이 여전히 취해지고 쓰기 충돌을 처리해야하기 때문에 쓰기 트랜잭션에 SI를 사용하면 이점이 거의 없거나 전혀 없습니다. 그것이 당신이 원하는 것이 아니라면 :)

참고 : RCSI (활성화 된 읽기 커밋에서 실행되는 모든 트랜잭션에 적용)와 달리 SI는을 사용하여 명시 적으로 요청해야합니다 SET TRANSACTION ISOLATION SNAPSHOT;.

작성자를 차단하는 독자에 의존 하는 미묘한 동작 (트리거 코드 포함)은 테스트를 필수적으로 만듭니다. 자세한 내용은 링크 된 기사 시리즈 및 온라인 설명서를 참조하십시오. RCSI를 결정하는 경우 특히 읽기 커밋 된 스냅 샷 격리 에서 데이터 수정 을 검토하십시오 .

마지막으로 인스턴스가 SQL Server 2008 서비스 팩 4에 패치되어 있는지 확인해야합니다.


0

겸손하게, 나는 병합을 사용하지 않을 것입니다. IF Exists (UPDATE) ELSE (INSERT)를 사용하겠습니다. 행을 식별하는 데 사용하는 두 개의 열이있는 클러스터 된 키가 있으므로 테스트하기 쉽습니다.

MASSIVE 삽입을 언급했지만 1 x 1을 수행합니다. 준비 테이블에서 데이터를 일괄 처리하고 POWER OVERWHELMING SQL 데이터 세트 전원을 사용하여 한 번 에 두 개 이상의 업데이트 / 삽입을 수행한다고 생각하십니까? 준비 테이블의 내용을 정기적으로 테스트하고 한 번에 1이 아닌 한 번에 상위 10000을 잡는 것처럼 ...

내 업데이트에서 이와 같은 작업을 수행합니다.

DECLARE @Set TABLE (StagingKey, ID,DATE)
INSERT INTO @Set
UPDATE Staging 
SET InProgress = 1
OUTPUT StagingKey, Staging.ID, Staging.Date
WHERE InProgress = 0
AND StagingID IN (SELECT TOP (100000) StagingKey FROM Staging WHERE inProgress = 0 ORDER BY StagingKey ASC ) --FIFO

DECLARE @Temp 
INSERT INTO @TEMP 
UPDATE [DEST] SET Value = Staging.Value [whatever]
OUTPUT INSERTED.ID, DATE [row identifiers]
FROM [DEST] 
JOIN [STAGING]
JOIN [@SET]; 
INSERT INTO @TEMP 
INSERT [DEST] 
SELECT
OUTPUT INSERT.ID, DATE [row identifiers] 
FROM [STAGING] 
JOIN [@SET] 
LEFT JOIN [DEST]

UPDATE Staging
SET inProgress = NULL
FROM Staging 
JOIN @set
ON @Set.Key = Staging.Key
JOIN @temp
ON @temp.id = @set.ID
AND @temp.date = @set.Date

업데이트 배치를 팝업하는 여러 작업을 실행할 수 있으며 물방울 삭제를 실행하는 별도의 작업이 필요합니다.

while exists (inProgress is null) 
delete top (100) from staging where inProgress is null 

준비 테이블을 정리합니다.

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