Фрагментация индекса при непрерывной обработке

10

SQL Server 2005

Мне нужно иметь возможность непрерывно обрабатывать около 350 миллионов записей в таблице записей 900 миллионов. Запрос, который я использую для выбора записей для обработки, становится сильно фрагментированным, когда я обрабатываю, и мне нужно остановить обработку, чтобы перестроить индекс. Псевдо модель данных и запрос ...

/**************************************/
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] обновляется («X» изменяется на «A») примерно в 10% случаев.
Столбец [DataStatus] обновляется менее 1% времени.

На данный момент мое решение состоит в том, чтобы выбрать первичный ключ всех записей для обработки в отдельной таблице обработки. Я удаляю ключи по мере их обработки, чтобы в качестве фрагментов индекса я имел дело с меньшим количеством записей.

Однако это не соответствует рабочему процессу, который я хочу иметь, так что эти данные обрабатываются непрерывно, без ручного вмешательства и значительных простоев. Я ожидаю простоя на ежеквартальной основе по дому. Но теперь, без отдельной таблицы обработки, я не могу обработать даже половину набора данных без фрагментации, становящейся настолько плохой, что требует остановки и перестройки индекса.

Любые рекомендации по индексации или другой модели данных? Есть шаблон, который мне нужно исследовать?
У меня есть полный контроль над моделью данных и программным обеспечением процесса, так что ничего не выходит за рамки.

Крис Галлуччи
источник
Одна мысль тоже: ваш индекс, кажется, неправильный порядок: он должен быть наиболее избирательным или наименее избирательным. Так ProcessThreadId, ProcessDate, DataStatus, DataType возможно?
ГБН
Мы объявили об этом в нашем чате. Очень хороший вопрос chat.stackexchange.com/rooms/179/the-heap
gbn
Я обновил запрос, чтобы получить более точное представление о выборе. Я несколько параллельных потоков, работающих под этим. Я принял к сведению рекомендацию выборочного заказа. Спасибо.
Крис Галлуччи
@ChrisGallucci Приходите поболтать, если сможете ...
JNK

Ответы:

4

Что вы делаете, вы используете таблицу в качестве очереди. Ваше обновление является методом удаления. Но кластерный индекс в таблице - плохой выбор для очереди. Использование таблиц в качестве очередей фактически предъявляет довольно жесткие требования к оформлению таблицы. Ваш кластеризованный индекс должен быть порядком удаления, в этом случае вероятно ([DataType], [DataStatus], [ProcessDate]). Вы можете реализовать первичный ключ как некластеризованное ограничение. Удалите некластеризованный индекс Idx, так как кластерный ключ играет свою роль.

Еще одна важная часть головоломки - поддерживать постоянный размер строки во время обработки. Вы объявили ProcessThreadIdкак a, VARCHAR(100)что означает, что строка увеличивается и сжимается во время «обработки», поскольку значение поля изменяется с NULL на ненулевое. Этот шаблон роста и сжатия в строке вызывает разбиение страницы и фрагментацию. Я не могу представить себе идентификатор потока, который является 'VARCHAR (100)'. Используйте тип фиксированной длины, возможно INT.

Как примечание, вам не нужно удалять из очереди в два этапа (ОБНОВЛЕНИЕ, а затем ВЫБОР). Вы можете использовать предложение 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 ;
/**************************************/

Кроме того, я бы рассмотрел перемещение успешно обработанных элементов в другую архивную таблицу. Вы хотите, чтобы ваши таблицы очередей колебались около нулевого размера, вы не хотите, чтобы они росли, поскольку они сохраняют «историю» от ненужных старых записей. В [ProcessDate]качестве альтернативы можно также рассмотреть разделение на (например, один текущий активный раздел, который действует как очередь и хранит записи с NULL ProcessDate, и другой раздел для всего, не равного NULL. Или несколько разделов для ненулевого, если вы хотите реализовать эффективный удаляет (отключает) данные, которые прошли обязательный срок хранения.[DataType] если он обладает достаточной избирательностью, но такой дизайн будет действительно сложным, так как он требует разбиения по постоянному вычисляемому столбцу (составной столбец, который склеивает вместе [DataType] и [ProcessingDate]).

Ремус Русану
источник
3

Я хотел бы начать, перемещая ProcessDateи Processthreadidполя в другой таблице.

Прямо сейчас, каждая строка, которую вы выбираете из этого довольно широкого индекса, также должна обновляться.

Если вы перенесете эти два поля в другую таблицу, объем обновлений в основной таблице будет сокращен на 90%, что должно позаботиться о большей части фрагментации.

Вы по-прежнему будете иметь фрагментацию в новой таблице, но вам будет легче управлять более узкой таблицей с гораздо меньшим количеством данных.

JNK
источник
Это и физическое разделение данных на основе [DataType] должно привести меня туда, где я должен быть. Сейчас я нахожусь в стадии разработки (на самом деле, переделки), так что пройдет некоторое время, прежде чем я получу возможность протестировать это изменение.
Крис Галлуччи