요청을 올바르게 이해하면 목표는 행의 배치를 삭제하는 동시에 DML 작업이 테이블 전체의 행에서 발생하는 것입니다. 목표는 배치를 삭제하는 것입니다. 그러나 해당 배치에 의해 정의 된 범위 내에 포함 된 기본 행이 잠겨 있으면 해당 배치를 건너 뛰고 다음 배치로 이동해야합니다. 그런 다음 이전에 삭제되지 않은 배치로 돌아가서 원래 삭제 논리를 다시 시도해야합니다. 필요한 모든 배치 행이 삭제 될 때까지이주기를 반복해야합니다.
앞에서 언급했듯이 블로킹 된 행을 포함 할 수있는 과거 범위를 건너 뛰려면 READPAST 힌트와 READ COMMITTED (기본값) 격리 수준을 사용하는 것이 좋습니다. 한 단계 더 나아가 SERIALIZABLE 격리 수준과 니블 링 삭제를 사용하는 것이 좋습니다.
SQL Server는 키 범위 잠금을 사용하여 직렬화 가능한 트랜잭션 격리 수준을 사용하는 동안 Transact-SQL 문에서 읽고있는 레코드 세트에 암시 적으로 포함 된 행 범위를 보호합니다. 자세한 내용은 여기를 참조하십시오 :
https://technet.microsoft.com /en-US/library/ms191272(v=SQL.105).aspx
니블 링 삭제를 통해 목표는 다양한 행을 분리하고 삭제하는 동안 해당 행에 변경 사항이 발생하지 않도록하는 것입니다. 즉, 팬텀 읽기 또는 삽입을 원하지 않습니다. 직렬화 가능한 격리 수준은이 문제를 해결하기위한 것입니다.
내 솔루션을 시연하기 전에 데이터베이스의 기본 격리 수준을 SERIALIZABLE로 전환하는 것이 권장되지 않으며 내 솔루션이 최고라고 권장하지도 않습니다. 나는 단지 그것을 제시하고 우리가 여기서 어디로 갈 수 있는지 알고 싶습니다.
몇 가지 하우스 키핑 노트 :
- 사용중인 SQL Server 버전은 Microsoft SQL Server 2012-11.0.5343.0 (X64)입니다.
- 내 테스트 데이터베이스가 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 격리 수준에 대한 몇 가지 추가 예제를 작성했습니다. 아래 링크에서 사용 가능해야합니다.
작업 삭제
삽입 작업
평등 작업-다음 키 값에 대한 키 범위 잠금
평등 운영-기존 데이터의 싱글 톤 페치
평등 운영-존재하지 않는 데이터의 싱글 톤 페치
불평등 작업-범위 및 다음 키 값에 대한 키 범위 잠금