Tengo una tabla de datos SQL con la siguiente estructura:
CREATE TABLE Data(
Id uniqueidentifier NOT NULL,
Date datetime NOT NULL,
Value decimal(20, 10) NULL,
RV timestamp NOT NULL,
CONSTRAINT PK_Data PRIMARY KEY CLUSTERED (Id, Date)
)
El número de identificadores distintos varía de 3000 a 50000.
El tamaño de la tabla varía hasta más de mil millones de filas.
One Id puede cubrir entre unas pocas filas hasta el 5% de la tabla.
La consulta más ejecutada en esta tabla es:
SELECT Id, Date, Value, RV
FROM Data
WHERE Id = @Id
AND Date Between @StartDate AND @StopDate
Ahora tengo que implementar la recuperación incremental de datos en un subconjunto de ID, incluidas las actualizaciones.
Luego usé un esquema de solicitud en el que la persona que llama proporciona una versión de fila específica, recupera un bloque de datos y usa el valor máximo de versión de fila de los datos devueltos para la llamada posterior.
He escrito este procedimiento:
CREATE TYPE guid_list_tbltype AS TABLE (Id uniqueidentifier not null primary key)
CREATE PROCEDURE GetData
@Ids guid_list_tbltype READONLY,
@Cursor rowversion,
@MaxRows int
AS
BEGIN
SELECT A.*
FROM (
SELECT
Data.Id,
Date,
Value,
RV,
ROW_NUMBER() OVER (ORDER BY RV) AS RN
FROM Data
inner join (SELECT Id FROM @Ids) Ids ON Ids.Id = Data.Id
WHERE RV > @Cursor
) A
WHERE RN <= @MaxRows
END
Donde @MaxRows
oscilará entre 500,000 y 2,000,000 dependiendo de qué tan fragmentado el cliente quiera sus datos.
He intentado diferentes enfoques:
- Indización en (Id, RV):
CREATE NONCLUSTERED INDEX IDX_IDRV ON Data(Id, RV) INCLUDE(Date, Value);
Utilizando el índice, la consulta busca las filas en las que RV = @Cursor
para cada uno Id
de @Ids
, leen las siguientes filas a continuación, se funden el resultado y tipo.
La eficiencia depende entonces de la posición relativa del @Cursor
valor.
Si está cerca del final de los datos (ordenado por RV), la consulta es instantánea y, si no, la consulta puede demorar hasta minutos (nunca permita que se ejecute hasta el final).
El problema con este enfoque es que @Cursor
está cerca del final de los datos y el orden no es doloroso (ni siquiera es necesario si la consulta devuelve menos filas que @MaxRows
) o está más atrás y la consulta tiene que ordenar las @MaxRows * LEN(@Ids)
filas.
- Indexación en RV:
CREATE NONCLUSTERED INDEX IDX_RV ON Data(RV) INCLUDE(Id, Date, Value);
Usando el índice, la consulta busca la fila donde RV = @Cursor
luego lee cada fila descartando los Ids no solicitados hasta que llegue @MaxRows
.
La eficiencia entonces depende del% de Ids solicitados ( LEN(@Ids) / COUNT(DISTINCT Id)
) y su distribución.
Más% Id solicitado significa menos filas descartadas, lo que significa lecturas más eficientes,% Id menos solicitado significa más filas descartadas, lo que significa más lecturas para la misma cantidad de filas resultantes.
El problema con este enfoque es que si los ID solicitados contienen solo unos pocos elementos, es posible que tenga que leer todo el índice para obtener las filas deseadas.
- Uso de índice filtrado o vistas indizadas
CREATE NONCLUSTERED INDEX IDX_RVClient1 ON Data(Id, RV) INCLUDE(Date, Value)
WHERE Id IN (/* list of Ids for specific client*/);
O
CREATE VIEW vDataClient1 WITH SCHEMABINDING
AS
SELECT
Id,
Date,
Value,
RV
FROM dbo.Data
WHERE Id IN (/* list of Ids for specific client*/)
CREATE UNIQUE CLUSTERED INDEX IDX_IDRV ON vDataClient1(Id, Rv);
Este método permite una indexación perfectamente eficiente y planes de ejecución de consultas, pero viene con desventajas: 1. Prácticamente, tendré que implementar SQL dinámico para crear los índices o vistas y modificar el procedimiento de solicitud para usar el índice o vista correctos. 2. Tendré que mantener un índice o vista por cliente existente, incluido el almacenamiento. 3. Cada vez que un cliente tendrá que modificar su lista de ID solicitados, tendré que soltar el índice o la vista y volver a crearlo.
Parece que no puedo encontrar un método que se adapte a mis necesidades.
Estoy buscando mejores ideas para implementar la recuperación incremental de datos. Esas ideas podrían implicar reelaborar el esquema de solicitud o el esquema de la base de datos, aunque preferiría un mejor enfoque de indexación si existe.
fuente
Value
columna. @crokusek: No ordenar por RV, ID en lugar de RV solo aumentará la carga de trabajo de clasificación sin ningún beneficio, no entiendo el razonamiento detrás de su comentario. Por lo que he leído, RV debería ser único a menos que inserte datos específicamente en esa columna, lo que no hace la aplicación.Respuestas:
Una solución es que la aplicación del cliente recuerde el máximo
rowversion
por ID. El tipo de tabla definida por el usuario cambiaría a:La consulta en el procedimiento puede reescribirse para usar el
APPLY
patrón (consulte mis artículos de SQLServerCentral parte 1 y parte 2 : se requiere inicio de sesión gratuito). La clave para un buen rendimiento aquí esORDER BY
: evita la búsqueda previa desordenada en la unión de bucles anidados. EstoRECOMPILE
es necesario para permitir que el optimizador vea la cardinalidad de la variable de tabla en el momento de la compilación (probablemente resultando en un plan paralelo deseable).Debería obtener un plan de consulta posterior a la ejecución como este (el plan estimado será serial):
fuente
MAX(RV)
por Id (o un sistema de suscripción donde la aplicación interna recuerda todos los pares Id / RV) y yo uso este patrón para otro cliente. Otra solución era obligar al cliente a recuperar siempre todos los ID (lo que hace que el problema de indexación sea trivial). Todavía no cubre la pregunta necesidad particular: recuperación incremental de un subconjunto de ID con un solo contador global proporcionado por el cliente.Si es posible, rediseñaría la mesa. Si podemos tener VersionNumber como un entero incremental sin espacios, que la tarea de recuperar el siguiente fragmento es un escaneo de rango totalmente trivial. Todo lo que necesitamos es el siguiente índice:
Por supuesto, debemos asegurarnos de que VersionNumber comience con uno y no tenga huecos. Esto es fácil de hacer con restricciones.
fuente
VersionNumber
? En cualquier caso, no puedo ver cómo eso ayudará con la pregunta, ¿podría dar más detalles?Lo que hubiera hecho:
En este caso, su PK debe ser un campo de identidad "clave sustituta" que se incremente automáticamente.
Como ya está en los miles de millones, sería mejor ir con un BigInt.
Llamémoslo DataID .
Esta voluntad:
Configure su nuevo BigInt PK ( DataID ) para usar un índice agrupado :
Esto:
Cree un índice no agrupado alrededor (Fecha, Id).
Esta voluntad:
Cree un índice no agrupado en (RV, ID).
Esta voluntad:
fuente