Ho una tabella di dati SQL con la seguente struttura:
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)
)
Il numero di ID distinti varia da 3000 a 50000.
La dimensione della tabella varia fino a oltre un miliardo di righe.
Un ID può coprire tra poche righe fino al 5% della tabella.
La singola query più eseguita su questa tabella è:
SELECT Id, Date, Value, RV
FROM Data
WHERE Id = @Id
AND Date Between @StartDate AND @StopDate
Ora devo implementare il recupero incrementale dei dati su un sottoinsieme di ID, inclusi gli aggiornamenti.
Ho quindi utilizzato uno schema di richiesta in cui il chiamante fornisce una specifica rowversion, recupera un blocco di dati e utilizza il valore massimo di rowversion dei dati restituiti per la chiamata successiva.
Ho scritto questa procedura:
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
Dove @MaxRows
andrà da 500.000 a 2.000.000 a seconda di come il cliente vorrà i suoi dati.
Ho provato diversi approcci:
- Indicizzazione su (Id, RV):
CREATE NONCLUSTERED INDEX IDX_IDRV ON Data(Id, RV) INCLUDE(Date, Value);
Utilizzando l'indice, la query cerca le righe in cui RV = @Cursor
per ciascuna Id
in @Ids
, legge le seguenti righe quindi unisce il risultato e ordina.
L'efficienza dipende quindi dalla posizione relativa del @Cursor
valore.
Se è vicino alla fine dei dati (ordinata da RV) la query è istantanea e in caso contrario la query può richiedere fino a minuti (mai lasciarla funzionare fino alla fine).
il problema con questo approccio è che @Cursor
è vicino alla fine dei dati e l'ordinamento non è doloroso (nemmeno necessario se la query restituisce meno righe di @MaxRows
) o è più indietro e la query deve ordinare le @MaxRows * LEN(@Ids)
righe.
- Indicizzazione su camper:
CREATE NONCLUSTERED INDEX IDX_RV ON Data(RV) INCLUDE(Id, Date, Value);
Utilizzando l'indice, la query cerca la riga in cui RV = @Cursor
quindi legge ogni riga scartando gli ID non richiesti fino a raggiungere @MaxRows
.
L'efficienza dipende quindi dalla% degli ID richiesti ( LEN(@Ids) / COUNT(DISTINCT Id)
) e dalla loro distribuzione.
L'ID% più richiesto significa meno righe scartate, il che significa letture più efficienti, l'ID% meno richiesto significa righe più scartate, il che significa più letture per lo stesso numero di righe risultanti.
Il problema con questo approccio è che se gli ID richiesti contengono solo pochi elementi, potrebbe essere necessario leggere l'intero indice per ottenere le righe desiderate.
- Utilizzo dell'indice filtrato o delle viste indicizzate
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);
Questo metodo consente piani di indicizzazione e di esecuzione delle query perfettamente efficienti, ma presenta degli svantaggi: 1. In pratica, dovrò implementare SQL dinamico per creare gli indici o le viste e modificare la procedura di richiesta per utilizzare l'indice o la vista corretti. 2. Dovrò mantenere un indice o una vista dal client esistente, incluso lo spazio di archiviazione. 3. Ogni volta che un cliente dovrà modificare il suo elenco di ID richiesti, dovrò eliminare l'indice o visualizzarlo e ricrearlo.
Non riesco a trovare un metodo adatto alle mie esigenze.
Sto cercando idee migliori per implementare il recupero incrementale dei dati. Quelle idee potrebbero implicare una rielaborazione dello schema richiedente o dello schema del database, anche se preferirei un approccio di indicizzazione migliore se ce n'è uno.
Value
colonna. @crokusek: non ordinando per camper, ID anziché camper aumenta solo il carico di lavoro di ordinamento senza alcun vantaggio, non capisco il ragionamento alla base del tuo commento. Da quello che ho letto, RV dovrebbe essere unico a meno che non si inseriscano dati specifici in quella colonna, cosa che l'applicazione no.