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% времени.
На данный момент мое решение состоит в том, чтобы выбрать первичный ключ всех записей для обработки в отдельной таблице обработки. Я удаляю ключи по мере их обработки, чтобы в качестве фрагментов индекса я имел дело с меньшим количеством записей.
Однако это не соответствует рабочему процессу, который я хочу иметь, так что эти данные обрабатываются непрерывно, без ручного вмешательства и значительных простоев. Я ожидаю простоя на ежеквартальной основе по дому. Но теперь, без отдельной таблицы обработки, я не могу обработать даже половину набора данных без фрагментации, становящейся настолько плохой, что требует остановки и перестройки индекса.
Любые рекомендации по индексации или другой модели данных? Есть шаблон, который мне нужно исследовать?
У меня есть полный контроль над моделью данных и программным обеспечением процесса, так что ничего не выходит за рамки.
источник
Ответы:
Что вы делаете, вы используете таблицу в качестве очереди. Ваше обновление является методом удаления. Но кластерный индекс в таблице - плохой выбор для очереди. Использование таблиц в качестве очередей фактически предъявляет довольно жесткие требования к оформлению таблицы. Ваш кластеризованный индекс должен быть порядком удаления, в этом случае вероятно
([DataType], [DataStatus], [ProcessDate])
. Вы можете реализовать первичный ключ как некластеризованное ограничение. Удалите некластеризованный индексIdx
, так как кластерный ключ играет свою роль.Еще одна важная часть головоломки - поддерживать постоянный размер строки во время обработки. Вы объявили
ProcessThreadId
как a,VARCHAR(100)
что означает, что строка увеличивается и сжимается во время «обработки», поскольку значение поля изменяется с NULL на ненулевое. Этот шаблон роста и сжатия в строке вызывает разбиение страницы и фрагментацию. Я не могу представить себе идентификатор потока, который является 'VARCHAR (100)'. Используйте тип фиксированной длины, возможноINT
.Как примечание, вам не нужно удалять из очереди в два этапа (ОБНОВЛЕНИЕ, а затем ВЫБОР). Вы можете использовать предложение OUTPUT, как описано в статье, приведенной выше:
Кроме того, я бы рассмотрел перемещение успешно обработанных элементов в другую архивную таблицу. Вы хотите, чтобы ваши таблицы очередей колебались около нулевого размера, вы не хотите, чтобы они росли, поскольку они сохраняют «историю» от ненужных старых записей. В
[ProcessDate]
качестве альтернативы можно также рассмотреть разделение на (например, один текущий активный раздел, который действует как очередь и хранит записи с NULL ProcessDate, и другой раздел для всего, не равного NULL. Или несколько разделов для ненулевого, если вы хотите реализовать эффективный удаляет (отключает) данные, которые прошли обязательный срок хранения.[DataType]
если он обладает достаточной избирательностью, но такой дизайн будет действительно сложным, так как он требует разбиения по постоянному вычисляемому столбцу (составной столбец, который склеивает вместе [DataType] и [ProcessingDate]).источник
Я хотел бы начать, перемещая
ProcessDate
иProcessthreadid
поля в другой таблице.Прямо сейчас, каждая строка, которую вы выбираете из этого довольно широкого индекса, также должна обновляться.
Если вы перенесете эти два поля в другую таблицу, объем обновлений в основной таблице будет сокращен на 90%, что должно позаботиться о большей части фрагментации.
Вы по-прежнему будете иметь фрагментацию в новой таблице, но вам будет легче управлять более узкой таблицей с гораздо меньшим количеством данных.
источник