web-dev-qa-db-fra.com

Détection des modifications dans une table SQL Server

Dans mon application, avec une base de données exécutée sur SQL Server 2012, j'ai un travail (tâche planifiée) qui exécute périodiquement une requête coûteuse et écrit les résultats dans une table qui peut ensuite être interrogée par l'application.

Idéalement, je voudrais exécuter cette requête coûteuse uniquement si quelque chose a changé depuis la dernière exécution de la requête. Étant donné que les tables source sont très grandes, je ne peux pas simplement sélectionner une somme de contrôle sur toutes les colonnes candidates ou quelque chose comme ça.

J'ai les idées suivantes:

  • Écrivez explicitement un dernier horodatage modifié, un indicateur "doit être des requêtes", ou quelque chose comme ça dans une table de suivi chaque fois que je change quelque chose dans une table source.
  • Utilisez un déclencheur pour faire de même.

Cependant, j'aimerais vraiment savoir s'il existe un moyen léger de détecter les modifications sur une table sans que je suive explicitement les écritures. Puis-je, par exemple, obtenir le "courant" ROWVERSION d'une table ou quelque chose comme ça?

13
Fabian Schmied

Non, il n'y en a pas. Toute sorte de suivi "dernière mise à jour à" se heurterait à un grave problème de performances car toutes les mises à jour, de toutes les transactions, tenteraient de mettre à jour le seul enregistrement suivant la "dernière mise à jour à". Cela signifierait en fait qu'une seule transaction peut mettre à jour la table à tout moment, et toutes les autres transactions doivent attendre que la première soit validée . Sérialisation complète. Le nombre d'administrateurs/développeurs prêts à supporter une telle pénalité de performance juste pour le bénéfice de savoir quand la dernière mise à jour s'est produite est probablement faible.

Vous êtes donc obligé de le gérer via un code personnalisé. Cela signifie des déclencheurs puisque l'alternative (détection à partir des enregistrements de journal) est une prérogative réservée uniquement à la réplication transactionnelle (ou c'est CDC alter-ego). Sachez que si vous essayez de le suivre via une colonne `` dernière mise à jour à '', vous serez alors confronté exactement au problème de sérialisation mentionné ci-dessus. Si la mise à jour simultanée est importante, vous devez utiliser un mécanisme de file d'attente (le déclencheur utilise un INSERT puis un processus agrège les valeurs insérées pour formuler la `` dernière mise à jour à ''). N'essayez pas de tricher avec une solution "intelligente" comme se faufiler sur l'identité actuelle ou rechercher sys.dm_db_index_usage_stats . Et aussi une colonne par enregistrement "updated_at", comme Rails horodatages ont, ne fonctionne pas car elle ne détecte pas les suppressions ...

Existe-t-il une alternative "légère"? En fait, il y en a un, mais il est difficile de dire s'il fonctionnera pour vous et il est difficile de le faire correctement: Notifications de requête . La notification de requête fait exactement cela, elle établira une notification si des données ont changé et que vous devez actualiser votre requête. Bien que la plupart des développeurs ne connaissent que son incarnation .Net en tant que SqlDependency, la notification de requête peut être utilisée comme un mécanisme persistant de longue durée pour détecter les modifications de données. Comparé au véritable suivi des changements, il sera vraiment léger et sa sémantique sera plus proche de vos besoins (quelque chose, quoi que ce soit , modifié, vous devez donc réexécutez la requête).

Mais à la fin, à votre place, je reconsidérerais vraiment mes hypothèses et retournerais à la planche à dessin. Vous pouvez peut-être utiliser l'envoi de journaux ou la réplication pour configurer une base de données de rapports sur un autre serveur. Ce que j'ai lu entre les lignes, c'est que vous avez besoin d'un pipeline ETL approprié et d'un entrepôt de données analytiques ...

14
Remus Rusanu

On dirait que j'ai deux ans de retard dans le jeu, ici, mais il y a en effet une façon assez légère de faire ce que vous demandez.

Il existe deux mécanismes SQL Server qui peuvent vous aider. Votre solution ultime pourrait être un hybride des deux.

Suivi des modifications . SQL Server a la capacité de placer des tables spécifiques sous surveillance, enregistrant uniquement les lignes qui ont changé (par leur valeur de clé primaire) et le type de changement qu'il a été (Insérer, Mettre à jour ou Supprimer). Une fois que vous avez configuré la détection des modifications sur un ensemble de tables, une requête légère peut vous indiquer si des modifications ont été apportées à la table depuis la dernière vérification. La surcharge est à peu près la même que la maintenance d'un index simple supplémentaire.

Rowversion/timestamp. Il s'agit d'un type de colonne varbinaire de 8 octets (pouvant être converti en BigInt) qui est incrémenté, à l'échelle de la base de données, chaque fois qu'une ligne qui en contient une est insérée ou mise à jour (cela ne facilite pas les suppressions). Si vous avez indexé ces colonnes, vous pouvez facilement savoir si les données de ligne ont changé en comparant le MAX (horodatage) à sa valeur depuis la dernière évaluation. Étant donné que la valeur augmente de façon monotone, cela vous donnerait une indication fiable que les données ont changé si la nouvelle valeur est plus grande que lors de la dernière vérification.

8
Curt

Si la source est en insertion uniquement, donnez-lui une colonne IDENTITY. Lorsque vous effectuez votre transfert de données, vous enregistrez la valeur la plus élevée écrite. Lors du prochain transfert, il vous suffit de rechercher des valeurs supérieures à celles enregistrées lors du transfert précédent. Nous le faisons pour transférer des enregistrements de journal vers un entrepôt de données.

Pour les lignes pouvant être mises à jour, ajoutez un indicateur "sale". Il aura trois valeurs - propre, sale et supprimé. Les requêtes quotidiennes devront omettre des lignes avec l'indicateur défini sur "supprimé". Cela coûtera cher en maintenance, en test et en temps d'exécution. Après la grande requête, vous mentionnez que toutes les lignes marquées pour suppression doivent être supprimées et le drapeau réinitialisé pour toutes les autres. Cela n'évolue pas bien.

Une alternative plus légère à Change Data Capture est Change Tracking . Il ne vous dira pas quelles valeurs ont changé, juste que la ligne a changé depuis sa dernière requête. Les fonctions intégrées facilitent la récupération des valeurs modifiées et la gestion du suivi. Nous avons réussi à utiliser CT pour traiter environ 100 000 modifications par jour dans une table de 100 000 000 lignes.

Les notifications de requête agissent toujours à un levier plus élevé - au niveau d'un ensemble de résultats. Conceptuellement, c'est comme définir une vue. Si SQL Server détecte que toute ligne renvoyée via cette vue a changé, il envoie un message à l'application. Il n'y a aucune indication sur le nombre de lignes modifiées ni sur les colonnes. Il n'y a qu'un simple message disant "quelque chose s'est produit". Il appartient à la demande de se renseigner et de réagir. Pratiquement, c'est beaucoup plus complexe que cela, comme vous pouvez l'imaginer. Il existe des restrictions sur la façon dont la requête peut être définie et la notification peut se déclencher pour des conditions autres que les données modifiées. Lorsque la notification se déclenche, elle est supprimée. Si d'autres activités intéressantes se produisent par la suite, aucun autre message ne sera envoyé. Il appartient au concepteur de l'application de s'assurer que l'activité entre une notification et le rétablissement ultérieur de la requête est correctement gérée.

Dans le contexte de la question du PO, QN aura l'avantage d'être peu coûteux à installer et peu coûteux en temps d'exécution. Il peut être important de mettre en place et de maintenir un régime rigoureux d'abonnement-réaction-message. Étant donné que le tableau de données est volumineux, il est probable qu'il y aura des modifications fréquentes, ce qui signifie que la notification est susceptible de se déclencher dans la plupart des cycles de traitement. Comme il n'y a aucune indication de ce qui a changé, le traitement incrémentiel des deltas ne sera pas possible, comme ce serait le cas avec CT ou CDC. Les frais généraux dus à un faux déclenchement sont fastidieux, mais même dans le pire des cas, la requête coûteuse n'a pas besoin d'être exécutée plus fréquemment qu'elle ne l'est actuellement.

7
Michael Green

SqlTableDependency

SqlTableDependency est un composant d'implémentation de haut niveau pour accéder aux notifications contenant des valeurs d'enregistrement de table sur la base de données SQL Server.

SqlTableDependency est un composant C # générique utilisé pour recevoir des notifications lorsque le contenu d'une table de base de données spécifiée change.

Quelle est la différence avec .NET SqlDepenency?

Fondamentalement, la principale différence est que SqlTableDependency envoie des événements contenant des valeurs pour l'enregistrement inséré, modifié ou supprimé, ainsi que l'opération DML (insertion/suppression/mise à jour) exécutée sur la table: SqlDepenency ne dit pas quelles données ont été modifiées sur le table de base de données, ils disent seulement que quelque chose a changé.

Jetez un oeil à projet GITHUB .

3

Si les mises à jour que vous attendez affectent un index (et uniquement si), vous pouvez utiliser la table système sys.dm_db_index_usage_stats pour détecter la dernière mise à jour d'un index sur la table en question. Vous utiliseriez le last_user_update champ.

Par exemple, pour obtenir les tableaux les plus récemment mis à jour:

select
    object_name(object_id) as OBJ_NAME, *
from
    sys.dm_db_index_usage_stats
where
    database_id = db_id(db_name())
order by
    dm_db_index_usage_stats.last_user_update desc

Ou, pour vérifier si une table spécifique a été modifiée depuis une date spécifique:

select
    case when count(distinct object_id) > 0 then 1 else 0 end as IS_CHANGED
from
    sys.dm_db_index_usage_stats
where
    database_id = db_id(db_name())
    and object_id = object_id('MY_TABLE_NAME')
    and last_user_update > '2016-02-18'
1
Geoff