Sql Server에서 선택한 행 그룹이 잠겨 있는지 확인하는 방법이 있습니까?


21

수십억 행 테이블에서 많은 수의 레코드를 업데이트 / 삭제하려고합니다. 이 테이블은 널리 사용되는 테이블이므로이 테이블의 여러 섹션에 많은 활동이 있습니다. 대규모 업데이트 / 삭제 활동은 모든 행 또는 페이지 잠금 또는 테이블 잠금에 대한 잠금을 기다리기 때문에 오랜 시간 동안 차단되어 작업을 완료하는 데 시간이 초과되거나 며칠이 걸립니다.

따라서 한 번에 작은 행 배치를 삭제하는 방법을 변경하고 있습니다. 그러나 선택한 (100 또는 1000 또는 2000 행이라고 가정) 현재 다른 프로세스에 의해 잠겨 있는지 확인하고 싶습니다.

  • 그렇지 않은 경우 삭제 / 업데이트를 진행하십시오.
  • 이들이 잠겨 있으면 다음 레코드 그룹으로 이동하십시오.
  • 결국, 처음으로 돌아와서 남은 것을 업데이트 / 삭제하십시오.

이것이 가능합니까?

고마워, ToC


2
delete 문 또는 NOWAIT (전체 그룹 실패)의 일부로 READPAST를 살펴 보셨습니까? 이 중 하나가 당신을 위해 일할 수 있습니다. msdn.microsoft.com/en-us/library/ms187373.aspx
숀 사라 Chipps 제거 말한다

@SeanGallardy 나는 그 아이디어를 고려하지 않았지만 이제는 그렇게 할 것입니다. 그러나 특정 행이 잠겨 있는지 여부를 확인하는 더 쉬운 방법이 있습니까? 감사.
ToC

3
LOCK_TIMEOUT ( msdn.microsoft.com/en-us/library/ms189470.aspx )을 살펴볼 수도 있습니다 . 예를 들어, Adam Machanic의 sp_whoisactive는 실행 계획을 수집 할 때 차단 될 경우 프로 시저가 너무 오래 기다리지 않도록하는 방법입니다. 짧은 시간 종료를 설정하거나 0 값을 사용할 수도 있습니다 ( "0은 잠금이 발생하자마자 전혀 기다리지 않고 메시지를 리턴 함을 의미합니다."). TRY / CATCH와 결합하여 오류 1222 ( "잠금 요청 시간 초과 기간이 초과되었습니다")하고 다음 배치로 진행하십시오.
Geoff Patterson

@gpatterson 흥미로운 접근 방식. 나도 이것을 시도 할 것이다.
TOC

2
대답하기 위해 응용 프로그램에서 특별히 수행하지 않은 한 행이 잠겨 있는지 확인하는 더 쉬운 방법은 없습니다. 기본적으로 먼저 lock_timeout이 설정된 HOLDLOCK 및 XLOCK으로 선택을 수행 할 수 있습니다 (원래 주석의 NOWAIT는 시간 제한을 0으로 설정). 당신이 그것을 얻지 못하면 무언가가 잠겨 있음을 알 수 있습니다. "색인 Z를 사용하여 표 Y의 X 행이 무언가 잠겼습니다."라고 쉽게 말할 있는 것은 없습니다 . 테이블에 잠금이 있는지 또는 페이지 / 행 / 키 / 등에 잠금이 있는지 확인할 수 있지만 쿼리에서 특정 행으로 변환하는 것은 쉽지 않습니다.
숀 사라 Chipps 제거 말한다

답변:


10

요청을 올바르게 이해하면 목표는 행의 배치를 삭제하는 동시에 DML 작업이 테이블 전체의 행에서 발생하는 것입니다. 목표는 배치를 삭제하는 것입니다. 그러나 해당 배치에 의해 정의 된 범위 내에 포함 된 기본 행이 잠겨 있으면 해당 배치를 건너 뛰고 다음 배치로 이동해야합니다. 그런 다음 이전에 삭제되지 않은 배치로 돌아가서 원래 삭제 논리를 다시 시도해야합니다. 필요한 모든 배치 행이 삭제 될 때까지이주기를 반복해야합니다.

앞에서 언급했듯이 블로킹 된 행을 포함 할 수있는 과거 범위를 건너 뛰려면 READPAST 힌트와 READ COMMITTED (기본값) 격리 수준을 사용하는 것이 좋습니다. 한 단계 더 나아가 SERIALIZABLE 격리 수준과 니블 링 삭제를 사용하는 것이 좋습니다.

SQL Server는 키 범위 잠금을 사용하여 직렬화 가능한 트랜잭션 격리 수준을 사용하는 동안 Transact-SQL 문에서 읽고있는 레코드 세트에 암시 적으로 포함 된 행 범위를 보호합니다. 자세한 내용은 여기를 참조하십시오 : https://technet.microsoft.com /en-US/library/ms191272(v=SQL.105).aspx

니블 링 삭제를 통해 목표는 다양한 행을 분리하고 삭제하는 동안 해당 행에 변경 사항이 발생하지 않도록하는 것입니다. 즉, 팬텀 읽기 또는 삽입을 원하지 않습니다. 직렬화 가능한 격리 수준은이 문제를 해결하기위한 것입니다.

내 솔루션을 시연하기 전에 데이터베이스의 기본 격리 수준을 SERIALIZABLE로 전환하는 것이 권장되지 않으며 내 솔루션이 최고라고 권장하지도 않습니다. 나는 단지 그것을 제시하고 우리가 여기서 어디로 갈 수 있는지 알고 싶습니다.

몇 가지 하우스 키핑 노트 :

  1. 사용중인 SQL Server 버전은 Microsoft SQL Server 2012-11.0.5343.0 (X64)입니다.
  2. 내 테스트 데이터베이스가 FULL 복구 모델을 사용하고 있습니다

실험을 시작하기 위해 테스트 데이터베이스, 샘플 테이블을 설정하고 테이블을 2,000,000 행으로 채 웁니다.


USE [master];
GO

SET NOCOUNT ON;

IF DATABASEPROPERTYEX (N'test', N'Version') > 0
BEGIN
    ALTER DATABASE [test] SET SINGLE_USER
        WITH ROLLBACK IMMEDIATE;
    DROP DATABASE [test];
END
GO

-- Create the test database
CREATE DATABASE [test];
GO

-- Set the recovery model to FULL
ALTER DATABASE [test] SET RECOVERY FULL;

-- Create a FULL database backup
-- in order to ensure we are in fact using 
-- the FULL recovery model
-- I pipe it to dev null for simplicity
BACKUP DATABASE [test]
TO DISK = N'nul';
GO

USE [test];
GO

-- Create our table
IF OBJECT_ID('dbo.tbl','U') IS NOT NULL
BEGIN
    DROP TABLE dbo.tbl;
END;
CREATE TABLE dbo.tbl
(
      c1 BIGINT IDENTITY (1,1) NOT NULL
    , c2 INT NOT NULL
) ON [PRIMARY];
GO

-- Insert 2,000,000 rows 
INSERT INTO dbo.tbl
    SELECT TOP 2000
        number
    FROM
        master..spt_values
    ORDER BY 
        number
GO 1000

이 시점에서 SERIALIZABLE 격리 수준의 잠금 메커니즘이 작동 할 수있는 하나 이상의 인덱스가 필요합니다.


-- Add a clustered index
CREATE UNIQUE CLUSTERED INDEX CIX_tbl_c1
    ON dbo.tbl (c1);
GO

-- Add a non-clustered index
CREATE NONCLUSTERED INDEX IX_tbl_c2 
    ON dbo.tbl (c2);
GO

이제 2,000,000 개의 행이 생성되었는지 확인하겠습니다.


SELECT
    COUNT(*)
FROM
    tbl;

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

따라서 데이터베이스, 테이블, 인덱스 및 행이 있습니다. 따라서 삭제 삭제에 대한 실험을 설정해 보겠습니다. 먼저 일반적인 니블 링 삭제 메커니즘을 만드는 방법을 결정해야합니다.


DECLARE
      @BatchSize        INT    = 100
    , @LowestValue      BIGINT = 20000
    , @HighestValue     BIGINT = 20010
    , @DeletedRowsCount BIGINT = 0
    , @RowCount         BIGINT = 1;

SET NOCOUNT ON;
GO

WHILE  @DeletedRowsCount <  ( @HighestValue - @LowestValue ) 
BEGIN

    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
    BEGIN TRANSACTION

        DELETE 
        FROM
            dbo.tbl 
        WHERE
            c1 IN ( 
                    SELECT TOP (@BatchSize)
                        c1
                    FROM
                        dbo.tbl 
                    WHERE 
                        c1 BETWEEN @LowestValue AND @HighestValue
                    ORDER BY 
                        c1
                  );

        SET @RowCount = ROWCOUNT_BIG();

    COMMIT TRANSACTION;

    SET @DeletedRowsCount += @RowCount;
    WAITFOR DELAY '000:00:00.025';
    CHECKPOINT;

END;

보다시피, 나는 명시적인 트랜잭션을 while 루프 안에 넣었습니다. 로그 플러시를 제한하려면 루프 외부에 자유롭게 배치하십시오. 또한 전체 복구 모델을 사용하므로 트랜잭션 로그가 크게 커지지 않도록하기 위해 니블 링 삭제 작업을 실행하는 동안 트랜잭션 로그 백업을 더 자주 만들 수 있습니다.

이 설정에는 몇 가지 목표가 있습니다. 먼저, 키 범위 잠금을 원합니다. 따라서 배치를 가능한 한 작게 유지하려고합니다. 또한 "거대한"테이블에서 동시성에 부정적인 영향을 미치고 싶지 않습니다. 그래서 자물쇠를 잡고 가능한 한 빨리 내버려두고 싶습니다. 따라서 배치 크기를 작게 만드는 것이 좋습니다.

이제이 삭제 루틴에 대한 간단한 예를 보여 드리겠습니다. SSMS 내에서 새 창을 열고 테이블에서 하나의 행을 삭제해야합니다. 기본 READ COMMITTED 격리 수준을 사용하여 암시 적 트랜잭션 내에서이 작업을 수행합니다.


DELETE FROM
    dbo.tbl
WHERE
    c1 = 20005;

이 행이 실제로 삭제 되었습니까?


SELECT
    c1
FROM
    dbo.tbl
WHERE
    c1 BETWEEN 20000 AND 20010;

예, 삭제되었습니다.

삭제 된 행 증명

이제 잠금을 확인하기 위해 SSMS 내에서 새 창을 열고 코드 스 니펫을 추가하겠습니다. Adam Mechanic의 sp_whoisactive를 사용하고 있습니다. sp_whoisactive


SELECT
    DB_NAME(resource_database_id) AS DatabaseName
  , resource_type
  , request_mode
FROM
    sys.dm_tran_locks
WHERE
    DB_NAME(resource_database_id) = 'test'
    AND resource_type = 'KEY'
ORDER BY
    request_mode;

-- Our insert
sp_lock 55;

-- Our deletions
sp_lock 52;

-- Our active sessions
sp_whoisactive;

이제 시작할 준비가되었습니다. 새 SSMS 창에서 삭제 한 행 하나를 다시 삽입하려고하는 명시 적 트랜잭션을 시작하겠습니다. 동시에, 우리는 니블 링 삭제 작업을 시작합니다.

삽입 코드 :


BEGIN TRANSACTION

    SET IDENTITY_INSERT dbo.tbl ON;

    INSERT  INTO dbo.tbl
            ( c1 , c2 )
    VALUES
            ( 20005 , 1 );

    SET IDENTITY_INSERT dbo.tbl OFF;

--COMMIT TRANSACTION;

인서트로 시작하여 삭제 작업을 시작합니다. 키 범위 잠금 및 독점 잠금을 볼 수 있습니다.

범위 및 확장 잠금

삽입물이 다음과 같은 잠금을 생성했습니다.

인서트 잠금

니블 링 삭제 / 선택시 다음 잠금이 유지됩니다.

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

삽입물이 예상대로 삭제를 차단하고 있습니다.

블록 삽입 삭제

이제 insert 트랜잭션을 커밋하고 무슨 일이 일어 났는지 봅시다.

삭제 커밋

그리고 예상대로 모든 거래가 완료됩니다. 이제 삽입이 팬텀인지 또는 삭제 조작으로 제거되었는지 확인해야합니다.


SELECT
    c1
FROM
    dbo.tbl
WHERE
    c1 BETWEEN 20000 AND 20015;

실제로 삽입이 삭제되었습니다. 따라서 팬텀 삽입은 허용되지 않았습니다.

팬텀 인서트 없음

결론적으로,이 연습의 진정한 의도는 모든 단일 행, 페이지 또는 테이블 수준 잠금을 시도하고 추적하지 않으며 배치의 요소가 잠겨 있는지 여부를 결정하려고하므로 삭제 작업이 필요한지 여부를 생각합니다. 기다림. 그것은 질문자의 의도일지도 모른다. 그러나 그 임무는 허술하고 기본적으로 불가능하지는 않지만 비현실적입니다. 실제 목표는 우리가 잠금 장치로 배치 범위를 분리 한 다음 배치를 삭제하기 전에 원하지 않는 현상이 발생하지 않도록하는 것입니다. 직렬화 가능한 분리 레벨은이 목표를 달성합니다. 핵심은 니블을 작게 유지하고 트랜잭션 로그를 제어하고 원하지 않는 현상을 제거하는 것입니다.

속도를 원한다면 분할 할 수없고 가장 빠른 결과를 위해 파티션 전환을 사용할 수없는 엄청나게 깊은 테이블을 작성하지 마십시오. 속도의 핵심은 분할 및 병렬 처리입니다. 고통의 열쇠는 니블과 라이브 락입니다.

어떻게 생각하는지 알려주세요.

SERIALIZABLE 격리 수준에 대한 몇 가지 추가 예제를 작성했습니다. 아래 링크에서 사용 가능해야합니다.

작업 삭제

삽입 작업

평등 작업-다음 키 값에 대한 키 범위 잠금

평등 운영-기존 데이터의 싱글 톤 페치

평등 운영-존재하지 않는 데이터의 싱글 톤 페치

불평등 작업-범위 및 다음 키 값에 대한 키 범위 잠금


9

따라서 한 번에 작은 행 배치를 삭제하는 방법을 변경하고 있습니다.

작은 배치 또는 청크 를 삭제하는 것이 좋습니다 . 나는 것 추가 작은 waitfor delay '00:00:05'경우 - 데이터베이스의 복구 모델에 따라 FULL, 다음을 수행 log backup하고있는 경우 SIMPLE다음을 수행 A는 manual CHECKPOINT트랜잭션 로그의 복부 팽만 피하기 위해 - 일괄 사이.

그러나 선택한 (100 또는 1000 또는 2000 행이라고 가정) 현재 다른 프로세스에 의해 잠겨 있는지 확인하고 싶습니다.

당신이 말하는 것은 상자 밖으로 완전히 가능하지는 않습니다 (3 개의 글 머리 기호를 명심하십시오). 위의 제안 small batches + waitfor delay이 작동하지 않으면 (적절한 테스트를 수행 한 경우)을 사용할 수 있습니다 query HINT.

사용하지 마십시오 NOLOCK- 볼 KB / 308,886 , Itzik 벤 웨이 코 뮤니시하여 SQL 서버 읽기 일관성 문제 , 에 의해 아론 버트 랜드 - 모든 지역 NOLOCK 퍼팅SQL 서버 NOLOCK 힌트 및 다른 가난한 아이디어를 .

READPAST힌트는 시나리오에서 도움이 될 것입니다. READPAST힌트 의 요점은 행 수준 잠금이 있으면 SQL Server가 읽지 않는 것입니다.

데이터베이스 엔진이 다른 트랜잭션에 의해 잠겨있는 행을 읽지 않도록 지정합니다. 경우 READPAST지정된 행 레벨 잠금은 생략한다. 즉, 데이터베이스 엔진은 잠금이 해제 될 때까지 현재 트랜잭션을 차단하는 대신 행을 건너 뜁니다.

제한된 테스트 중에 DELETE from schema.tableName with (READPAST, READCOMMITTEDLOCK)쿼리 세션 격리 수준을 READ COMMITTED사용 SET TRANSACTION ISOLATION LEVEL READ COMMITTED하고 기본 격리 수준 인 사용으로 설정할 때 실제로 처리량이 매우 우수하다는 것을 알았 습니다.


2

질문에 대한 의견 으로 원래 제공된 다른 접근법을 요약 .


  1. NOWAIT호환되지 않는 잠금이 발생하자마자 원하는 동작이 전체 청크에 실패하는 경우 사용하십시오 .

    로부터 NOWAIT문서 :

    테이블에서 잠금이 발생하자마자 메시지를 반환하도록 데이터베이스 엔진에 지시합니다. 특정 테이블 NOWAIT에 지정하는 것과 같습니다 SET LOCK_TIMEOUT 0. NOWAIT때 힌트가 작동하지 않습니다 TABLOCK힌트도 포함되어 있습니다. TABLOCK힌트를 사용할 때 기다리지 않고 쿼리를 종료하려면 SETLOCK_TIMEOUT 0;대신 쿼리 앞에 머리말을 붙이십시오 .

  2. SET LOCK_TIMEOUT유사한 결과를 달성하기 위해 사용 하지만 구성 가능한 시간 초과

    로부터 SET LOCK_TIMEOUT문서

    명령문이 잠금이 해제되기를 기다리는 시간 (밀리 초)을 지정합니다.

    잠금 대기가 시간 종료 값을 초과하면 오류가 리턴됩니다. 값이 0이면 잠금이 발생하는 즉시 전혀 기다리지 않고 메시지를 반환한다는 의미입니다.


0

두 개의 병렬 쿼리가 있다고 가정합니다.

연결 / 세션 1 : 행을 잠급니다 = 777

SELECT * FROM your_table WITH(UPDLOCK,READPAST) WHERE id = 777

연결 / 세션 2 : 잠긴 행을 무시합니다 = 777

SELECT * FROM your_table WITH(UPDLOCK,READPAST) WHERE id = 777

OR 연결 / 세션 2 : 예외 발생

DECLARE @id integer;
SELECT @id = id FROM your_table WITH(UPDLOCK,READPAST) WHERE id = 777;
IF @id is NULL
  THROW 51000, 'Hi, a record is locked or does not exist.', 1;

-1

이와 같은 것을 필터링 해보십시오 . 정말로 구체적으로 만들고 싶다면 복잡해질 수 있습니다 . sys.dm_tran_locks 에 대한 설명은 BOL을 참조하십시오.

SELECT 
tl.request_session_id,
tl.resource_type,
tl.resource_associated_entity_id,
db_name(tl.resource_database_id) 'Database',
CASE 
    WHEN tl.resource_type = 'object' THEN object_name(tl.resource_associated_entity_id, tl.resource_database_id)
    ELSE NULL
END 'LockedObject',
tl.resource_database_id,
tl.resource_description,
tl.request_mode,
tl.request_type,
tl.request_status FROM [sys].[dm_tran_locks] tl WHERE resource_database_id <> 2order by tl.request_session_id

그냥 궁금해-왜 downvote?
rottengeek

-11

삭제하는 동안 NoLOCK을 사용할 수 있으며 행이 잠겨 있으면 삭제되지 않습니다. 이상적이지는 않지만 트릭을 수행 할 수 있습니다.

DELETE TA FROM dbo.TableA TA WITH (NOLOCK) WHERE Condition = True

7
내가 얻을 내 로컬 컴퓨터에 그것을 시도 할 경우 Msg 1065, Level 15, State 1, Line 15 The NOLOCK and READUNCOMMITTED lock hints are not allowed for target tables of INSERT, UPDATE, DELETE or MERGE statements., 2005 년 이후 사용되지
팀 모니카 - 톰 V
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.