J'ai une table de données SQL avec la structure suivante:
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)
)
Le nombre d'ID distincts varie de 3000 à 50000.
La taille de la table varie jusqu'à plus d'un milliard de lignes.
Un identifiant peut couvrir entre quelques lignes jusqu'à 5% du tableau.
La requête la plus exécutée sur cette table est:
SELECT Id, Date, Value, RV
FROM Data
WHERE Id = @Id
AND Date Between @StartDate AND @StopDate
Je dois maintenant implémenter la récupération incrémentielle des données sur un sous-ensemble d'ID, y compris les mises à jour.
J'ai ensuite utilisé un schéma de demande dans lequel l'appelant fournit une version de ligne spécifique, récupère un bloc de données et utilise la valeur de version de ligne maximale des données renvoyées pour l'appel suivant.
J'ai écrit cette procédure:
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
Où @MaxRows
se situera entre 500 000 et 2 000 000 selon la façon dont le client voudra ses données.
J'ai essayé différentes approches:
- Indexation sur (Id, RV):
CREATE NONCLUSTERED INDEX IDX_IDRV ON Data(Id, RV) INCLUDE(Date, Value);
À l'aide de l'index, la requête recherche les lignes où RV = @Cursor
pour chaque Id
entrée @Ids
, lit les lignes suivantes, puis fusionne le résultat et trie.
L'efficacité dépend alors de la position relative de la @Cursor
valeur.
Si elle est proche de la fin des données (commandée par RV), la requête est instantanée et sinon la requête peut prendre jusqu'à quelques minutes (ne jamais la laisser s'exécuter jusqu'à la fin).
le problème avec cette approche est qu'il @Cursor
est proche de la fin des données et que le tri n'est pas pénible (même pas nécessaire si la requête retourne moins de lignes que @MaxRows
), soit il est plus loin et la requête doit trier les @MaxRows * LEN(@Ids)
lignes.
- Indexation sur RV:
CREATE NONCLUSTERED INDEX IDX_RV ON Data(RV) INCLUDE(Id, Date, Value);
À l'aide de l'index, la requête recherche la ligne où RV = @Cursor
lire ensuite chaque ligne en rejetant les ID non demandés jusqu'à ce qu'elle atteigne @MaxRows
.
L'efficacité dépend alors du% des identifiants demandés ( LEN(@Ids) / COUNT(DISTINCT Id)
) et de leur distribution.
Plus l'Id% demandé signifie moins de lignes supprimées, ce qui signifie des lectures plus efficaces, moins l'Id% demandé signifie plus de lignes supprimées, ce qui signifie plus de lectures pour la même quantité de lignes résultantes.
Le problème avec cette approche est que si les identifiants demandés ne contiennent que quelques éléments, il peut être nécessaire de lire l'index entier pour obtenir les lignes souhaitées.
- Utilisation d'un index filtré ou de vues indexées
CREATE NONCLUSTERED INDEX IDX_RVClient1 ON Data(Id, RV) INCLUDE(Date, Value)
WHERE Id IN (/* list of Ids for specific client*/);
Ou
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);
Cette méthode permet une indexation et des plans d'exécution des requêtes parfaitement efficaces, mais présente des inconvénients: 1. Pratiquement, je devrai implémenter du SQL dynamique pour créer les index ou les vues et modifier la procédure de demande pour utiliser le bon index ou la bonne vue. 2. Je devrai maintenir un index ou une vue par client existant, y compris le stockage. 3. Chaque fois qu'un client devra modifier sa liste d'ID demandés, je devrai supprimer l'index ou le visualiser et le recréer.
Je n'arrive pas à trouver une méthode qui convienne à mes besoins.
Je recherche de meilleures idées pour implémenter la récupération incrémentielle des données. Ces idées pourraient impliquer de retravailler le schéma demandeur ou le schéma de base de données bien que je préfère une meilleure approche d'indexation s'il y en a une.
la source
Value
colonne. @crokusek: Ne pas commander par RV, ID au lieu de RV ne fait qu'augmenter la charge de travail de tri sans aucun avantage, je ne comprends pas le raisonnement derrière votre commentaire. D'après ce que j'ai lu, RV devrait être unique à moins d'insérer des données spécifiquement dans cette colonne, ce que l'application ne fait pas.Réponses:
Une solution consiste à ce que l'application cliente se souvienne du maximum
rowversion
par ID. Le type de table défini par l'utilisateur se changerait en:La requête dans la procédure peut ensuite être réécrite pour utiliser le
APPLY
modèle (voir mes articles SQLServerCentral partie 1 et partie 2 - connexion gratuite requise). La clé d'une bonne performance ici est laORDER BY
- elle évite la prélecture non ordonnée sur la jointure des boucles imbriquées. CeciRECOMPILE
est nécessaire pour permettre à l'optimiseur de voir la cardinalité de la variable de table au moment de la compilation (résultant probablement en un plan parallèle souhaitable).Vous devriez obtenir un plan de requête post-exécution comme celui-ci (le plan estimé sera en série):
la source
MAX(RV)
ID par (ou d'un système d'abonnement où l'application interne se souvient de toutes les paires ID / RV) et j'utilise ce motif pour un autre client. Une autre solution était de forcer le client à toujours récupérer tous les identifiants (ce qui rend le problème d'indexation trivial). Il ne couvre toujours pas la question du besoin particulier: récupération incrémentielle d'un sous-ensemble d'ID avec un seul compteur global fourni par le client.Si possible, je remanierais la table. Si nous pouvons avoir VersionNumber comme un entier incrémentiel sans lacunes, que la tâche de récupérer le morceau suivant est un balayage de plage totalement trivial. Tout ce dont nous avons besoin est l'index suivant:
Bien sûr, nous devons nous assurer que VersionNumber commence par un et n'a pas de lacunes. C'est facile à faire avec des contraintes.
la source
VersionNumber
? Dans les deux cas, je ne vois pas en quoi cela va aider avec la question, pourriez-vous développer davantage?Ce que j'aurais fait:
Dans ce cas, votre PK doit être un champ d'identité "Clé de substitution" qui s'incrémente automatiquement.
Puisque vous êtes déjà dans les milliards, il serait préférable d'aller avec un BigInt.
Appelons-le DataID .
Cette volonté:
Configurez votre nouveau BigInt PK ( DataID ) pour utiliser un index clusterisé:
Cela:
Créez un index non groupé autour de (Date, Id).
Cette volonté:
Créez un index non clusterisé sur (RV, ID).
Cette volonté:
la source