"알파 클러스터"를 참조하는 내 워크 스테이션 및 Enterprise Edition 2 노드 가상 컴퓨터 클러스터에서 SQL Server 2008 Developer Edition 실행
varbinary (max) 열이있는 행을 삭제하는 데 걸리는 시간은 해당 열의 데이터 길이와 직접 관련이 있습니다. 처음에는 직관적으로 들릴지 모르지만 조사 후 SQL Server가 실제로 행을 일반적으로 삭제하고 이러한 종류의 데이터를 처리하는 방법에 대한 이해와 충돌합니다.
이 문제는 .NET 웹 응용 프로그램에서 볼 수있는 삭제 시간 초과 (> 30 초) 문제에서 비롯된 것으로,이 설명을 위해 단순화했습니다.
레코드가 삭제되면 SQL Server는 트랜잭션이 커밋 된 후 나중에 고스트 정리 작업으로 정리할 고스트로 표시합니다 ( Paul Randal의 블로그 참조 ). varbinary (max) 열에서 각각 16KB, 4MB 및 50MB 데이터를 가진 3 개의 행을 삭제하는 테스트에서 데이터의 행 내 부분과 트랜잭션이있는 페이지에서 발생하는 것으로 나타났습니다. 로그.
이상한 점은 X 잠금이 삭제 중에 모든 LOB 데이터 페이지에 배치되고 페이지가 PFS에 할당 해제된다는 것입니다. 트랜잭션 로그와 DMV sp_lock
결과 및 결과를 볼 수 있습니다 dm_db_index_operational_stats
( page_lock_count
).
이렇게하면 워크 스테이션과 알파 클러스터에서 해당 페이지가 버퍼 캐시에없는 경우 I / O 병목 현상이 발생합니다. 실제로, page_io_latch_wait_in_ms
동일한 DMV로부터의 것은 실질적으로 전체 삭제 기간이며, page_io_latch_wait_count
이는 잠금 된 페이지 수와 일치합니다. 내 워크 스테이션의 50MB 파일의 경우 빈 버퍼 캐시 ( checkpoint
/ dbcc dropcleanbuffers
)로 시작할 때 3 초 이상으로 변환되며 , 조각화가 많고로드가 너무 길면 더 길어질 것입니다.
캐시에 공간을 할당하는 것이 아니라는 것을 확인하려고했습니다. checkpoint
메서드 대신 삭제를 실행하기 전에 다른 행에서 2GB의 데이터를 읽었으며 이는 SQL Server 프로세스에 할당 된 것보다 큽니다. SQL Server가 데이터를 섞는 방법을 모르기 때문에 유효한 테스트인지 확실하지 않습니다. 나는 그것이 새로운 것을 찬성하여 항상 오래된 것을 밀어 낼 것이라고 생각했다.
또한 페이지를 수정하지도 않습니다. 이것으로 볼 수 있습니다 dm_os_buffer_descriptors
. 삭제 후 페이지는 깨끗하지만 수정 된 페이지 수는 세 번의 소규모, 중간 및 대규모 삭제 모두에서 20 미만입니다. 또한 DBCC PAGE
조회 된 페이지의 샘플링 에 대한 출력을 비교 했으며 변경 사항이 없었습니다 ( ALLOCATED
비트 만 PFS에서 제거됨). 그것은 단지 그들을 할당 해제합니다.
페이지 조회 / 할당 해제로 인해 문제가 발생했음을 증명하기 위해 바닐라 varbinary (max) 대신 파일 스트림 열을 사용하여 동일한 테스트를 시도했습니다. LOB 크기에 관계없이 삭제 시간은 일정했습니다.
먼저, 나의 학문적 질문들 :
- SQL Server가 X를 잠그기 위해 모든 LOB 데이터 페이지를 찾아야하는 이유는 무엇입니까? 메모리에 잠금이 어떻게 표시되는지에 대한 세부 사항입니까 (페이지와 함께 저장 됨)? 이는 완전히 캐시되지 않은 경우 I / O 영향이 데이터 크기에 크게 의존하게합니다.
- X가 할당을 해제하기 위해 왜 잠기는가? 할당 해제가 페이지 자체를 수정할 필요가 없기 때문에 행 내 부분으로 인덱스 리프만 잠그는 것으로 충분하지 않습니까? 잠금 장치가 보호하는 LOB 데이터를 얻는 다른 방법이 있습니까?
- 이미 이러한 유형의 작업에 대한 백그라운드 작업이 있다고 가정 할 때 페이지를 미리 할당 해제해야하는 이유는 무엇입니까?
그리고 아마도 더 중요한 실제 질문 :
- 삭제 작업을 다르게 수행 할 수있는 방법이 있습니까? 내 목표는 파일 스트림과 비슷한 크기에 관계없이 일정한 시간 동안 삭제하여 사실 후에 백그라운드에서 정리가 발생한다는 것입니다. 구성적인 것입니까? 물건을 이상하게 보관하고 있습니까?
설명 된 테스트 (SSMS 쿼리 창을 통해 실행)를 재현하는 방법은 다음과 같습니다.
CREATE TABLE [T] (
[ID] [uniqueidentifier] NOT NULL PRIMARY KEY,
[Data] [varbinary](max) NULL
)
DECLARE @SmallID uniqueidentifier
DECLARE @MediumID uniqueidentifier
DECLARE @LargeID uniqueidentifier
SELECT @SmallID = NEWID(), @MediumID = NEWID(), @LargeID = NEWID()
-- May want to keep these IDs somewhere so you can use them in the deletes without var declaration
INSERT INTO [T] VALUES (@SmallID, CAST(REPLICATE(CAST('a' AS varchar(max)), 16 * 1024) AS varbinary(max)))
INSERT INTO [T] VALUES (@MediumID, CAST(REPLICATE(CAST('a' AS varchar(max)), 4 * 1024 * 1024) AS varbinary(max)))
INSERT INTO [T] VALUES (@LargeID, CAST(REPLICATE(CAST('a' AS varchar(max)), 50 * 1024 * 1024) AS varbinary(max)))
-- Do this before test
CHECKPOINT
DBCC DROPCLEANBUFFERS
BEGIN TRAN
-- Do one of these deletes to measure results or profile
DELETE FROM [T] WHERE ID = @SmallID
DELETE FROM [T] WHERE ID = @MediumID
DELETE FROM [T] WHERE ID = @LargeID
-- Do this after test
ROLLBACK
내 워크 스테이션에서 삭제 프로파일 링의 결과는 다음과 같습니다.
| 열 유형 | 크기 삭제 | 기간 (ms) | 읽는다 | 쓰기 | CPU | -------------------------------------------------- ------------------ | VarBinary | 16KB | 40 | 13 | 2 | 0 | | VarBinary | 4MB | 952 | 2318 | 2 | 0 | | VarBinary | 50MB | 2976 | 28594 | 1 | 62 | -------------------------------------------------- ------------------ | FileStream | 16KB | 1 | 12 | 1 | 0 | | FileStream | 4MB | 0 | 9 | 0 | 0 | | FileStream | 50MB | 1 | 9 | 0 | 0 |
다음과 같은 이유로 반드시 파일 스트림을 사용할 수는 없습니다.
- 우리의 데이터 크기 분포는 그것을 보증하지 않습니다.
- 실제로 많은 청크로 데이터를 추가하며 파일 스트림은 부분 업데이트를 지원하지 않습니다. 우리는 이것을 중심으로 디자인해야합니다.
업데이트 1
삭제의 일부로 데이터가 트랜잭션 로그에 기록되고 있다는 이론을 테스트했지만 실제로는 그렇지 않습니다. 이것을 잘못 테스트하고 있습니까? 아래를 참조하십시오.
SELECT MAX([Current LSN]) FROM fn_dblog(NULL, NULL)
--0000002f:000001d9:0001
BEGIN TRAN
DELETE FROM [T] WHERE ID = @ID
SELECT
SUM(
DATALENGTH([RowLog Contents 0]) +
DATALENGTH([RowLog Contents 1]) +
DATALENGTH([RowLog Contents 3]) +
DATALENGTH([RowLog Contents 4])
) [RowLog Contents Total],
SUM(
DATALENGTH([Log Record])
) [Log Record Total]
FROM fn_dblog(NULL, NULL)
WHERE [Current LSN] > '0000002f:000001d9:0001'
크기가 5MB를 초과하는 파일의 경우을 반환했습니다 1651 | 171860
.
또한 데이터가 로그에 기록되면 페이지 자체가 더러워 질 것으로 예상합니다. 할당 해제 만 기록 된 것으로 보이며, 삭제 후 더티 한 내용과 일치합니다.
업데이트 2
폴 랜달에게서 답장을 받았습니다. 그는 트리를 탐색하고 할당 해제 할 페이지를 찾기 위해 모든 페이지를 읽어야한다는 사실을 확인했으며 어떤 페이지를 찾아 볼 다른 방법이 없다고 말했습니다. 이것은 1 & 2에 대한 반 답변입니다 (행 외부 데이터에 대한 잠금의 필요성을 설명하지는 않지만 작은 감자입니다).
질문 3이 여전히 열려 있습니다. 삭제를 위해 정리할 백그라운드 작업이 이미있는 경우 페이지를 할당 해제해야하는 이유는 무엇입니까?
그리고 물론 모든 중요한 질문 :이 크기에 따른 삭제 동작을 직접 완화 (즉 해결하지 못하는 방법) 할 수 있습니까? SQL Server에서 50MB 행을 저장하고 삭제하는 유일한 사람이 아니라면 이것이 더 일반적인 문제라고 생각합니까? 다른 모든 사람들이 가비지 수집 작업의 형태 로이 문제를 해결합니까?