Ich habe eine SQL-Datentabelle mit der folgenden Struktur:
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)
)
Die Anzahl der unterschiedlichen IDs reicht von 3000 bis 50000.
Die Größe der Tabelle variiert bis zu über einer Milliarde Zeilen.
Eine ID kann zwischen einigen Zeilen bis zu 5% der Tabelle abdecken.
Die am häufigsten ausgeführte Abfrage in dieser Tabelle lautet:
SELECT Id, Date, Value, RV
FROM Data
WHERE Id = @Id
AND Date Between @StartDate AND @StopDate
Ich muss jetzt das inkrementelle Abrufen von Daten für eine Teilmenge von IDs implementieren, einschließlich Aktualisierungen.
Ich habe dann ein Anforderungsschema verwendet, in dem der Anrufer eine bestimmte Zeilenversion bereitstellt, einen Datenblock abruft und den maximalen Zeilenversionswert der zurückgegebenen Daten für den nachfolgenden Aufruf verwendet.
Ich habe dieses Verfahren geschrieben:
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
Der @MaxRows
Bereich liegt zwischen 500.000 und 2.000.000, je nachdem, wie stark der Kunde seine Daten haben möchte.
Ich habe verschiedene Ansätze ausprobiert:
- Indizierung auf (Id, RV):
CREATE NONCLUSTERED INDEX IDX_IDRV ON Data(Id, RV) INCLUDE(Date, Value);
Mithilfe des Index sucht die Abfrage nach den Zeilen, in denen RV = @Cursor
für jedes Id
In @Ids
die folgenden Zeilen gelesen und dann das Ergebnis zusammengeführt und sortiert werden.
Die Effizienz hängt dann von der relativen @Cursor
Wertposition ab.
Wenn es kurz vor dem Ende der Daten steht (von RV bestellt), erfolgt die Abfrage sofort, und wenn nicht, kann die Abfrage bis zu Minuten dauern (lassen Sie sie niemals bis zum Ende laufen).
Das Problem bei diesem Ansatz ist, dass @Cursor
entweder das Ende der Daten erreicht ist und die Sortierung nicht schmerzhaft ist (nicht einmal erforderlich, wenn die Abfrage weniger Zeilen zurückgibt als @MaxRows
). Sie liegt auch weiter hinten und die Abfrage muss @MaxRows * LEN(@Ids)
Zeilen sortieren .
- Indizierung auf Wohnmobilen:
CREATE NONCLUSTERED INDEX IDX_RV ON Data(RV) INCLUDE(Id, Date, Value);
Mithilfe des Index sucht die Abfrage die Zeile, in der RV = @Cursor
dann jede Zeile gelesen wird, wobei die nicht angeforderten IDs verworfen werden, bis sie erreicht sind @MaxRows
.
Die Effizienz hängt dann vom Prozentsatz der angeforderten IDs ( LEN(@Ids) / COUNT(DISTINCT Id)
) und ihrer Verteilung ab.
Mehr angeforderte ID% bedeutet weniger verworfene Zeilen, was effizientere Lesevorgänge bedeutet, weniger angeforderte Id% bedeutet mehr verworfene Zeilen, was mehr Lesevorgänge für die gleiche Anzahl resultierender Zeilen bedeutet.
Das Problem bei diesem Ansatz besteht darin, dass, wenn die angeforderten IDs nur wenige Elemente enthalten, möglicherweise der gesamte Index gelesen werden muss, um die gewünschten Zeilen zu erhalten.
- Verwenden von gefiltertem Index oder indizierten Ansichten
CREATE NONCLUSTERED INDEX IDX_RVClient1 ON Data(Id, RV) INCLUDE(Date, Value)
WHERE Id IN (/* list of Ids for specific client*/);
Oder
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);
Diese Methode ermöglicht perfekt effiziente Indizierungs- und Abfrageausführungspläne, hat jedoch Nachteile: 1. In der Praxis muss ich dynamisches SQL implementieren, um die Indizes oder Ansichten zu erstellen und das Anforderungsverfahren zu ändern, um den richtigen Index oder die richtige Ansicht zu verwenden. 2. Ich muss einen Index oder eine Ansicht des vorhandenen Clients einschließlich des Speichers verwalten. 3. Jedes Mal, wenn ein Client seine Liste der angeforderten IDs ändern muss, muss ich den Index löschen oder anzeigen und neu erstellen.
Ich kann anscheinend keine Methode finden, die meinen Bedürfnissen entspricht.
Ich suche nach besseren Ideen, um das inkrementelle Abrufen von Daten zu implementieren. Diese Ideen könnten bedeuten, dass das anfordernde Schema oder das Datenbankschema überarbeitet werden, obwohl ich einen besseren Indizierungsansatz bevorzugen würde, wenn es einen gibt.
Value
Spalte. @crokusek: Wird nicht per Wohnmobil bestellt, ID statt Wohnmobil erhöht nur die Sortierarbeitsbelastung ohne Nutzen, ich verstehe die Gründe für Ihren Kommentar nicht. Nach dem, was ich gelesen habe, sollte RV eindeutig sein, es sei denn, Sie fügen Daten speziell in diese Spalte ein, was die Anwendung nicht tut.