Індексація фрагментації під час постійної обробки


10

SQL Server 2005

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

/**************************************/
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 можливо?
gbn

Ми рекламували це в нашому чаті. Дуже гарне запитання. chat.stackexchange.com/rooms/179/the-heap
gbn

Я оновив запит, щоб точніше представити вибір. Я декілька одночасних потоків, що виконують це Я відмітив рекомендацію щодо вибіркового замовлення. Дякую.
Кріс Галлуччі

@ChrisGallucci Приходьте до чату, якщо зможете ...
JNK

Відповіді:


4

Що ви робите, - це ви використовуєте таблицю в якості черги. Ваше оновлення - це метод dequeue. Але кластерний індекс на столі - це поганий вибір для черги. Використання таблиць як черг насправді пред'являє досить жорсткі вимоги до дизайну таблиці. Ваш кластерний індекс повинен бути порядком відмови, у цьому випадку ймовірним ([DataType], [DataStatus], [ProcessDate]). Ви можете реалізувати первинний ключ як необмежене обмеження. Відкиньте некластеризований індекс Idx, оскільки кластерний ключ виконує свою роль.

Ще одна важлива частина головоломки - це підтримувати постійний розмір рядка під час обробки. Ви оголосили ProcessThreadIdяк "a", VARCHAR(100)що означає, що рядок росте і скорочується під час "обробки", оскільки значення поля змінюється з NULL на ненулеве. Цей візерунок із зменшенням росту та зменшення у рядку викликає розбиття сторінки та фрагментацію. Я не можу уявити ідентифікатор потоку, який є "VARCHAR (100)". Використовуйте тип фіксованої довжини, можливо 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 ;
/**************************************/

Крім того, я б розглядав можливість переміщення успішно оброблених елементів в іншу, архівну таблицю. Ви хочете, щоб ваші таблиці очеретів наближалися до нульового розміру, ви не хочете, щоб вони зростали, оскільки вони зберігають "історію" від непотрібних старих записів. Ви також можете розглянути розділення [ProcessDate]як альтернативу (наприклад, один поточний активний розділ, який виступає в якості черги і зберігає записи з NULL ProcessDate, а інший розділ для всього ненульового. Або кілька розділів для ненульових, якщо ви хочете реалізувати ефективну ефективність видаляє (вимикає) дані, які пройшли мандатний термін зберігання. Якщо речі нагріваються, ви можете розділити їх додатково[DataType] якщо у нього достатня вибірковість, але ця конструкція була б дуже складною, оскільки вимагає розподілу на персистуюну обчислену колонку (складений стовпчик, який склеюється [DataType] та [ProcessingDate]).


3

Я хотів би почати, переміщаючи ProcessDateі Processthreadidполя в іншій таблиці.

Зараз кожний рядок, вибраний із цього досить широкого індексу, також повинен оновлюватися.

Якщо ви перемістите ці два поля в іншу таблицю, об'єм оновлення в головній таблиці скорочується на 90%, що повинно враховувати більшу частину фрагментації.

Ви все одно будете мати фрагментацію в таблиці НОВОГО, але вам буде легше керувати більш вузькою таблицею з набагато меншими даними.


Це і фізично розбивати дані на основі [DataType] повинно завести мене там, де мені потрібно бути. Наразі я перебуваю на етапі проектування (фактично переробленого), тому пройде деякий час, перш ніж я отримаю шанс протестувати цю зміну.
Кріс Галлуччі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.