지속적으로 처리하는 동안 인덱스 조각화


10

SQL Server 2005

900M 레코드 테이블에서 약 350M 레코드를 지속적으로 처리 할 수 ​​있어야합니다. 처리 할 레코드를 선택하는 데 사용하는 쿼리는 처리 할 때 잘못 조각화되며 인덱스를 다시 작성하기 위해 처리를 중지해야합니다. 의사 데이터 모델 및 쿼리 ...

/**************************************/
CREATE TABLE [Table] 
(
    [PrimaryKeyId] [INT] IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
    [ForeignKeyId] [INT] NOT NULL,
    /* more columns ... */
    [DataType] [CHAR](1) NOT NULL,
    [DataStatus] [DATETIME] NULL,
    [ProcessDate] [DATETIME] NOT NULL,
    [ProcessThreadId] VARCHAR (100) NULL
);

CREATE NONCLUSTERED INDEX [Idx] ON [Table] 
(
    [DataType],
    [DataStatus],
    [ProcessDate],
    [ProcessThreadId]
);
/**************************************/

/**************************************/
WITH cte AS (
    SELECT TOP (@BatchSize) [PrimaryKeyId], [ProcessThreadId]
    FROM [Table] WITH ( ROWLOCK, UPDLOCK, READPAST )
    WHERE [DataType] = 'X'
    AND [DataStatus] IS NULL
    AND [ProcessDate] < DATEADD(m, -2, GETDATE()) -- older than 2 months
    AND [ProcessThreadId] IS NULL
)
UPDATE cte
SET [ProcessThreadId] = @ProcessThreadId;

SELECT * FROM [Table] WITH ( NOLOCK )
WHERE [ProcessThreadId] = @ProcessThreadId;
/**************************************/

데이터 내용 ...
[DataType] 열이 CHAR (1)로 입력되는 동안 모든 레코드의 약 35 %가 'X'와 같고 나머지는 'A'입니다.
[DataType]이 'X'인 레코드 중 약 10 %는 NOT NULL [DataStatus] 값을 갖습니다.

처리 된 모든 레코드에 대해 [ProcessDate] 및 [ProcessThreadId] 열이 업데이트됩니다.
[DataType] 열이 10 % 정도 업데이트됩니다 ( 'X'가 'A'로 변경됨).
[DataStatus] 열이 시간의 1 % 미만으로 업데이트됩니다.

지금은 해결책이 별도의 처리 테이블로 처리 할 모든 레코드의 기본 키를 선택하는 것입니다. 키를 처리 할 때 키를 삭제하여 인덱스 조각으로 레코드 수를 줄입니다.

그러나 이것은 수동으로 개입하지 않고 상당한 다운 타임없이 이러한 데이터를 지속적으로 처리 할 수 ​​있도록 원하는 워크 플로에 맞지 않습니다. 하우스 키핑 작업에 대해 분기별로 다운 타임이 예상됩니다. 그러나 이제 별도의 처리 테이블이 없으면 조각화가 인덱스를 중지하고 다시 작성해야 할 정도로 나 빠지지 않으면 데이터 세트의 절반까지 처리 할 수 ​​없습니다.

인덱싱 또는 다른 데이터 모델에 대한 권장 사항이 있습니까? 조사해야 할 패턴이 있습니까?
데이터 모델과 프로세스 소프트웨어를 완전히 제어 할 수 있으므로 테이블에서 아무것도 나오지 않습니다.


한가지 생각도 : 인덱스가 잘못된 순서 인 것 같습니다 : 가장 덜 선택적인 것부터 선택해야합니다. ProcessThreadId, ProcessDate, DataStatus, DataType?
gbn

우리는 채팅에서 그것을 광고했습니다. 아주 좋은 질문입니다. chat.stackexchange.com/rooms/179/the-heap
gbn

선택을보다 정확하게 표현하기 위해 쿼리를 업데이트했습니다. 이것을 실행하는 동시 스레드가 여러 개 있습니다. 선택적 주문 권장 사항을 언급했습니다. 감사.
Chris Gallucci

@ChrisGallucci 당신이 할 수 있다면 채팅에 와서 ...
JNK

답변:


4

당신이하고있는 일은 테이블을 대기열로 사용하는 것입니다. 업데이트는 dequeue 방법입니다. 그러나 테이블의 클러스터형 인덱스는 큐에 적합하지 않습니다. 테이블을 대기열로 사용하면 실제로 테이블 디자인에 대한 엄격한 요구 사항이 적용됩니다. 클러스터형 인덱스 대기열에서 제외 되어야합니다([DataType], [DataStatus], [ProcessDate]) . 기본 키를 비 클러스터 제약 조건 으로 구현할 수 있습니다 . Idx클러스터 된 키가 역할을 수행 하므로 비 클러스터형 인덱스를 삭제하십시오 .

퍼즐의 또 다른 중요한 부분은 처리 중에 행 크기를 일정하게 유지하는 것입니다. 필드 값이 널 (NULL)에서 널 (null)이 아닌 값으로 변경되기 때문에 행이 '처리됨'에 따라 행이 커지거나 작아짐을 의미 하는 ProcessThreadId로를 선언했습니다 VARCHAR(100). 행의이 자르기 및 축소 패턴으로 인해 페이지가 분할되고 조각화됩니다. 'VARCHAR (100)'인 스레드 ID를 상상할 수 없습니다. 고정 길이 유형 (아마도)을 사용하십시오 INT.

참고로 두 단계 (업데이트 다음에 SELECT)로 대기열에서 제외시킬 필요는 없습니다. 위에 링크 된 기사에 설명 된대로 OUTPUT 절을 사용할 수 있습니다.

/**************************************/
CREATE TABLE [Table] 
(
    [PrimaryKeyId] [INT] IDENTITY(1,1) NOT NULL PRIMARY KEY NONCLUSTERED,
    [ForeignKeyId] [INT] NOT NULL,
    /* more columns ... */
    [DataType] [CHAR](1) NOT NULL,
    [DataStatus] [DATETIME] NULL,
    [ProcessDate] [DATETIME] NOT NULL,
    [ProcessThreadId] INT NULL
);

CREATE CLUSTERED INDEX [Cdx] ON [Table] 
(
    [DataType],
    [DataStatus],
    [ProcessDate]
);
/**************************************/

declare @BatchSize int, @ProcessThreadId int;

/**************************************/
WITH cte AS (
    SELECT TOP (@BatchSize) [PrimaryKeyId], [ProcessThreadId] , ... more columns 
    FROM [Table] WITH ( ROWLOCK, UPDLOCK, READPAST )
    WHERE [DataType] = 'X'
    AND [DataStatus] IS NULL
    AND [ProcessDate] < DATEADD(m, -2, GETDATE()) -- older than 2 months
    AND [ProcessThreadId] IS NULL
)
UPDATE cte
SET [ProcessThreadId] = @ProcessThreadId
OUTPUT DELETED.[PrimaryKeyId] , ... more columns ;
/**************************************/

또한 성공적으로 처리 된 항목을 다른 아카이브 테이블로 옮기는 것을 고려할 것입니다. 큐 테이블을 거의 0 크기로 가져 가려면 불필요한 이전 항목의 '이력'을 유지하면서 커지는 것을 원하지 않습니다. [ProcessDate]대안으로 대안으로 파티셔닝을 고려할 수도 있습니다 (예 : 대기열로 작동하고 NULL ProcessDate로 항목을 저장하는 현재 활성 파티션 하나와 널이 아닌 모든 항목에 대한 다른 파티션 또는 효율적으로 구현하려는 경우 널이 아닌 여러 파티션) 강제 보존 기간이 지난 데이터는 삭제 (스위치 아웃)됩니다.[DataType] 선택성이 충분하지만 지속 형 계산 열 ([DataType]과 [ProcessingDate]을 함께 결합하는 복합 열)을 사용하여 분할해야하기 때문에 해당 디자인은 실제로 복잡합니다.


3

ProcessDateand Processthreadid필드를 다른 테이블 로 이동하여 시작 합니다.

현재이 넓은 인덱스에서 선택한 모든 행도 업데이트해야합니다.

이 두 필드를 다른 테이블로 이동하면 기본 테이블의 업데이트 볼륨이 90 % 감소하므로 대부분의 조각화를 처리해야합니다.

NEW 테이블에는 여전히 조각화가 있지만 훨씬 적은 데이터로 더 좁은 테이블에서 관리하는 것이 더 쉽습니다.


이것과 [DataType]을 기준으로 데이터를 물리적으로 분할하면 필요한 곳에 도착할 수 있습니다. 나는 현재 이것의 설계 (실제 재 설계) 단계에 있기 때문에이 변화를 시험 할 기회를 얻기까지 어느 정도 시간이 걸립니다.
Chris Gallucci
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.