SQL Server 2005
Necesito poder procesar continuamente unos 350 millones de registros en una tabla de registros de 900 millones. La consulta que estoy usando para seleccionar los registros a procesar se fragmenta mucho a medida que proceso y tengo que detener el procesamiento para reconstruir el índice. Modelo de pseudodatos y consulta ...
/**************************************/
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;
/**************************************/
Contenido de datos ...
Mientras que la columna [DataType] se escribe como CHAR (1), aproximadamente el 35% de todos los registros equivalen a 'X' con el resto igual a 'A'.
De solo los registros donde [DataType] es igual a 'X', aproximadamente el 10% tendrá un valor NOT NULL [DataStatus].
Las columnas [ProcessDate] y [ProcessThreadId] se actualizarán para cada registro procesado.
La columna [DataType] se actualiza ('X' se cambia a 'A') aproximadamente el 10% del tiempo.
La columna [DataStatus] se actualiza menos del 1% del tiempo.
Por ahora, mi solución es seleccionar la clave principal de todos los registros para procesar en una tabla de procesamiento separada. Borro las claves a medida que las proceso para que, a medida que el índice se fragmente, esté lidiando con menos registros.
Sin embargo, esto no se ajusta al flujo de trabajo que quiero tener para que estos datos se procesen continuamente, sin intervención manual y tiempo de inactividad significativo. Preveo el tiempo de inactividad trimestralmente para las tareas domésticas. Pero ahora, sin la tabla de procesamiento separada, no puedo procesar ni siquiera la mitad del conjunto de datos sin que la fragmentación se vuelva tan grave que sea necesario detener y reconstruir el índice.
¿Alguna recomendación para indexar o un modelo de datos diferente? ¿Hay algún patrón que deba investigar?
Tengo el control total del modelo de datos y el software del proceso, por lo que no hay nada fuera de la mesa.
fuente
Respuestas:
Lo que estás haciendo es usar una tabla como cola. Su actualización es el método de eliminación de cola. Pero el índice agrupado en la tabla es una mala elección para una cola. El uso de tablas como colas en realidad impone requisitos bastante estrictos en el diseño de la tabla. Su índice agrupado debe ser el orden de la cola, en este caso probable
([DataType], [DataStatus], [ProcessDate])
. Puede implementar la clave primaria como una restricción no agrupada. Descarte el índice no agrupadoIdx
, ya que la clave agrupada toma su papel.Otra pieza importante del rompecabezas es mantener constante el tamaño de la fila durante el procesamiento. Ha declarado el
ProcessThreadId
como unVARCHAR(100)
que implica que la fila crece y se reduce a medida que se 'procesa' porque el valor del campo cambia de NULL a no nulo. Este patrón de crecimiento y reducción en la fila provoca divisiones y fragmentación de la página. No puedo imaginar una ID de hilo que sea 'VARCHAR (100)'. Use un tipo de longitud fija, quizás unINT
.Como nota al margen, no es necesario retirar la cola en dos pasos (ACTUALIZACIÓN seguido de SELECCIONAR). Puede usar la cláusula OUTPUT, como se explica en el artículo vinculado anteriormente:
Además, consideraría mover elementos procesados con éxito a una tabla diferente de archivo. Desea que sus tablas de cola se mantengan cerca del tamaño cero, no desea que crezcan ya que retienen el "historial" de las entradas antiguas innecesarias. También puede considerar la partición
[ProcessDate]
como una alternativa (es decir, una partición activa actual que actúa como la cola y almacena entradas con NULL ProcessDate, y otra partición para todo lo que no es nulo. O particiones múltiples para no nulo si desea implementar eficientemente elimina (cambia) los datos que han pasado el período de retención obligatorio. Si las cosas se ponen calientes, puede particionar además[DataType]
si tiene suficiente selectividad, pero ese diseño sería realmente complicado ya que requiere la partición por una columna computada persistente (una columna compuesta que une [DataType] y [ProcessingDate]).fuente
Comenzaría moviendo los campos
ProcessDate
yProcessthreadid
a otra tabla.En este momento, cada fila que seleccione de este índice bastante amplio también debe actualizarse.
Si mueve esos dos campos a otra tabla, su volumen de actualización en la tabla principal se reduce en un 90%, lo que debería encargarse de la mayor parte de la fragmentación.
Aún tendrá fragmentación en la NUEVA tabla, pero será más fácil de administrar en una tabla más estrecha con muchos menos datos.
fuente