Pourquoi les tables temporelles enregistrent-elles l'heure de début de la transaction?


8

Lors de la mise à jour d'une ligne dans une table temporelle, les anciennes valeurs de la ligne sont stockées dans la table d'historique avec l'heure de début de la transaction comme SysEndTime. Les nouvelles valeurs de la table actuelle auront l'heure de début de la transaction comme SysStartTime.

SysStartTimeet SysEndTimesont des datetime2colonnes utilisées par les tables temporelles pour enregistrer quand une ligne était la version actuelle. L'heure de début de la transaction est l'heure à laquelle la transaction contenant les mises à jour a commencé.

BOL dit:

Les heures enregistrées dans les colonnes système datetime2 sont basées sur l'heure de début de la transaction elle-même. Par exemple, toutes les lignes insérées dans une même transaction auront le même temps UTC enregistré dans la colonne correspondant au début de la période SYSTEM_TIME.

Exemple: je commence à mettre à jour toutes les lignes de ma table Orders à 20160707 11:00:00et la transaction prend 5 minutes pour s'exécuter. Cela crée une ligne dans la table d'historique pour chaque ligne avec SysEndTimeas 20160707 11:00:00. Toutes les lignes de la table actuelle auront un SysStartTimede 20160707 11:00:00.

Si quelqu'un devait exécuter une requête à 20160707 11:01:00(pendant la mise à jour), il verrait les anciennes valeurs (en supposant le niveau d'isolement validé par défaut).

Mais si quelqu'un devait alors utiliser la AS OFsyntaxe pour interroger la table temporelle telle qu'elle était, 20160707 11:01:00il verrait les nouvelles valeurs parce que la leur SysStartTimeserait 20160707 11:00:00.

Pour moi, cela signifie qu'il ne montre pas ces lignes telles qu'elles étaient à l'époque. S'il utilisait l'heure de fin de la transaction, le problème n'existerait pas.

Questions: est-ce par conception? Suis-je en train de manquer quelque chose?

La seule raison pour laquelle je peux penser qu'il utilise l'heure de début de la transaction est qu'il est le seul «connu» au début de la transaction. Il ne sait pas quand la transaction se terminera au début et il faudrait du temps pour appliquer l'heure de fin à la fin, ce qui invaliderait l'heure de fin qu'elle appliquait. Est-ce que ça a du sens?

Cela devrait vous permettre de recréer le problème.


1
Vous avez répondu à votre propre question, si vous utilisez l'heure de fin de la transaction, vous avez une autre mise à jour à la fin de la transaction: la mise à jour se termine 20160707 11:04:58et maintenant vous mettez à jour toutes les lignes avec cet horodatage. Mais cette mise à jour s'exécute également pendant quelques secondes et se termine à 20160707 11:05:02, maintenant, quel horodatage est la fin correcte de la transaction? Ou supposez que vous avez utilisé Read Uncommitedà 20160707 11:05:00et que les lignes ont été renvoyées, mais que plus tard AS OFne les affiche pas.
dnoeth

@dnoeth Oui, je suppose que cette «question» est plus une clarification de ma théorie.
James Anderson

Je n'ai pas plongé dans l'implémentation de SQL Server, mais Teradata a eu des tables bi-temporelles pendant des années et je recommande toujours de lire cette étude de cas de Richard Snodgrass (le gars qui a "inventé" les requêtes temporelles), elle est basée sur la syntaxe SQL pré-ANSI de Teradata , mais les concepts sont les mêmes: cs.ulb.ac.be/public/_media/teaching/infoh415/…
dnoeth

Réponses:


4

L'idée est de suivre le temps logique par rapport au temps physique. Logique se réfère simplement à ce qu'un utilisateur / une application attend du moment d'une insertion / mise à jour / suppression. Le fait que l'opération DML puisse prendre un certain temps pour une raison quelconque, n'est pas significatif ou même facilement déterminé et compris par un utilisateur. Si vous avez déjà eu à expliquer à un comptable (je l'ai) la contention du verrou contre le verrou, c'est une situation comparable.

Par exemple, lorsque Bob "dit" à l'application que tous les employés du département de Bob commenceront à gagner 42 $ / min 20160707 11:00:00, Bob (et ses employés) s'attend à ce que le salaire de chacun soit désormais calculé à 42 $ / min à partir de ce moment. Bob ne se soucie pas que pour que cela soit effectué, l'application doit effectuer 2 lectures et 6 écritures dans la base de données par employé et leurs données + fichiers journaux reposent sur un tas de disques RAID-5 SATA II, ce qui prend environ 7 minutes pour terminer la tâche pour les 256 employés de Bob. Bob, son comptable et le gestionnaire de la paie se soucient que tous ses employés soient payés 42 $ / min à partir 20160707 11:00:00. Sinon, les employés qui ont été mis à jour 20160707 11:00:01seront légèrement ennuyés tandis que ceux dont les dossiers ont été mis à jour 20160707 11:00:07se rassembleront à l'extérieur du service de la paie.

Il existe des cas d'utilisation valides pour suivre le temps physique, comme le débogage et la criminalistique, mais pour l'utilisateur final, cela n'a généralement aucun sens. Le Tlog conserve à la fois les informations de commande et de synchronisation pour chacune des opérations d'écriture (entre autres), donc il est là si vous savez comment regarder.


Bons points. Je suppose que la technologie n'est adaptée qu'à certains cas d'utilisation comme celui que vous mentionnez. Pour les raisons que je déclare ci-dessus, il semble que ce serait un mauvais ajustement à utiliser pour suivre les prix ou les valeurs des stocks qui peuvent changer en très peu de temps.
James Anderson

En fait non. C'est un problème de perf et d'échelle. Les tables temporelles fonctionnent toujours si vous avez besoin de garder un historique précis du cours de l'action. Il vous suffit de vous assurer que les inserts sont très granulaires et peuvent se terminer dans une très petite fenêtre. Sinon, les modifications ultérieures seront bloquées et si le débit entrant est suffisamment élevé, des délais d'attente se produisent et une perte potentielle de données si l'application ne peut pas gérer les tentatives. Si vous exécutez la DB hors fusion IO ou avec des tables optimisées en mémoire, vous pouvez facilement gérer des dizaines de milliers d'insertions par seconde à bien plus de cent mille par seconde.
SQLmojoe

3

Je pense qu'il s'agit en effet d'un défaut de conception, bien qu'il ne soit pas spécifique à SQL Server 2016, car toutes les autres implémentations existantes de tables temporelles (à ma connaissance) ont le même défaut. Les problèmes qui peuvent survenir avec les tables temporelles à cause de cela sont assez graves; le scénario dans votre exemple est doux par rapport à ce qui peut mal tourner en général:

Références de clé étrangère brisées : supposons que nous ayons deux tables temporelles, la table A ayant une référence de clé étrangère à la table B. Supposons maintenant que nous avons deux transactions, toutes deux exécutées au niveau d'isolement READ COMMITTED: la transaction 1 commence avant la transaction 2, la transaction 2 insère une ligne dans la table B et valide, puis la transaction 1 insère une ligne dans la table A avec une référence à la ligne nouvellement ajoutée de B. Puisque l'ajout de la nouvelle ligne à B a déjà été validé, la contrainte de clé étrangère est satisfaite et la transaction 1 est capable de s'engager avec succès. Cependant, si nous devions afficher la base de données "AS OF" quelque temps entre le début de la transaction 1 et le début de la transaction 2, nous verrions le tableau A avec une référence à une ligne de B qui n'existe pas. Donc dans ce cas,la table temporelle fournit une vue incohérente de la base de données . Ce n'était bien sûr pas l'intention de la norme SQL: 2011, qui stipule,

Les lignes système historiques dans un tableau versionné par le système forment des instantanés immuables du passé. Toutes les contraintes qui étaient en vigueur lors de la création d'une ligne système historique auraient déjà été vérifiées lorsque cette ligne était une ligne système actuelle, il n'est donc jamais nécessaire d'appliquer des contraintes sur les lignes système historiques.

Clés primaires non uniques : supposons que nous ayons une table avec une clé primaire et deux transactions, toutes deux à un niveau d'isolement READ COMMITTED, dans lequel ce qui suit se produit: après le début de la transaction 1 mais avant qu'elle touche cette table, la transaction 2 supprime un certain ligne du tableau et valide. Ensuite, la transaction 1 insère une nouvelle ligne avec la même clé primaire que celle qui a été supprimée. Cela se passe très bien, mais lorsque vous regardez la table AS OF un temps entre le début de la transaction 1 et le début de la transaction 2, nous verrons deux lignes avec la même clé primaire.

Erreurs lors des mises à jour simultanées : supposons que nous ayons une table et deux transactions qui mettent à jour la même ligne, toujours au niveau d'isolement READ COMMITTED. La transaction 1 commence en premier, mais la transaction 2 est la première à mettre à jour la ligne. La transaction 2 est ensuite validée, et la transaction 1 effectue ensuite une mise à jour différente sur la ligne et se valide. Tout va bien, sauf que s'il s'agit d'une table temporelle, lors de l'exécution de la mise à jour dans la transaction 1 lorsque le système va insérer la ligne requise dans la table d'historique, le SysStartTime généré sera l'heure de début de la transaction 2, tandis que le SysEndTime sera l'heure de début de la transaction 1, qui n'est pas un intervalle de temps valide puisque le SysEndTime serait avant le SysStartTime. Dans ce cas, SQL Server renvoie une erreur et annule la transaction (par exemple, consultezcette discussion ). C'est très désagréable, car au niveau d'isolement READ COMMITTED, on ne s'attend pas à ce que les problèmes de concurrence conduisent à des échecs purs et simples, ce qui signifie que les applications ne seront pas nécessairement prêtes à effectuer de nouvelles tentatives. En particulier, cela est contraire à une "garantie" dans la documentation de Microsoft:

Ce comportement garantit que vos applications héritées continueront de fonctionner lorsque vous activez la gestion des versions système sur des tables qui bénéficieront de la gestion des versions. ( lien )

D'autres implémentations de tables temporelles ont traité ce scénario (deux transactions simultanées mettant à jour la même ligne) en offrant une option pour "ajuster" automatiquement les horodatages s'ils ne sont pas valides (voir ici et ici ). Il s'agit d'une solution de contournement laide, car elle a la conséquence malheureuse de briser l'atomicité des transactions, car les autres déclarations au sein des mêmes transactions ne verront généralement pas leurs horodatages ajustés de la même manière; c'est-à-dire qu'avec cette solution de contournement, si nous affichons la base de données "AS OF" à certains moments, nous pouvons voir des transactions partiellement exécutées.

Solution: Vous avez déjà suggéré la solution évidente, qui consiste à ce que l'implémentation utilise l'heure de fin de la transaction (c'est-à-dire l'heure de validation) au lieu de l'heure de début. Oui, il est vrai que lorsque nous exécutons une instruction au milieu d'une transaction, il est impossible de savoir quel sera le temps de validation (comme c'est le cas à l'avenir, ou pourrait même ne pas exister si la transaction devait être roulée) retour). Mais cela ne signifie pas que la solution est impossible à mettre en œuvre; il faut juste le faire d'une manière différente. Par exemple, lors de l'exécution d'une instruction UPDATE ou DELETE, lors de la création de la ligne d'historique, le système pourrait simplement mettre l'ID de transaction en cours au lieu d'une heure de début, puis l'ID peut être converti en horodatage ultérieurement par le système après la validation de la transaction .

Dans le contexte de ce type d'implémentation, je suggère qu'avant que la transaction ne soit validée, toutes les lignes qu'elle ajoute à la table d'historique ne devraient pas être visibles par l'utilisateur. Du point de vue de l'utilisateur, il devrait simplement apparaître que ces lignes sont ajoutées (avec l'horodatage de la validation) au moment de la validation. En particulier, si la transaction n'est jamais validée, elle ne devrait jamais apparaître dans l'historique. Bien sûr, cela n'est pas conforme à la norme SQL: 2011 qui décrit les insertions dans l'historique (y compris les horodatages) comme se produisant au moment des instructions UPDATE et DELETE (par opposition au moment de la validation). Mais je ne pense pas que cela compte vraiment, étant donné que la norme n'a jamais été correctement mise en œuvre (et ne peut sans doute jamais l'être) en raison des problèmes décrits ci-dessus,

Du point de vue des performances, il peut sembler indésirable que le système doive revenir en arrière et revoir les lignes d'historique pour remplir l'horodatage de validation. Mais selon la façon dont cela se fait, le coût pourrait être assez faible. Je ne sais pas vraiment comment SQL Server fonctionne en interne, mais PostgreSQL utilise par exemple un journal d'écriture anticipée, ce qui fait que si plusieurs mises à jour sont effectuées sur les mêmes parties d'une table, ces mises à jour sont consolidées de sorte que le les données ne doivent être écrites qu'une seule fois dans les pages du tableau physique - et cela s'applique généralement dans ce scénario. Dans tout les cas,

Bien sûr, puisque (pour autant que je sache) ce type de système n'a jamais été implémenté, je ne peux pas dire avec certitude que cela fonctionnerait - peut-être que quelque chose me manque - mais je ne vois aucune raison pourquoi cela ne pouvait pas fonctionner.


0

Au moment où vous validez votre transaction, toutes les données doivent être écrites à l'intérieur des pages de données (en mémoire et sur le disque dans le fichier journal). Y compris SysStartTimeet SysEndTimecolonnes. Comment pouvez-vous connaître l'heure de fin de la transaction avant qu'elle ne soit réellement terminée?

À moins que vous ne puissiez prédire l'avenir, l'utilisation de l'heure de début des transactions est la seule option, même si elle peut être moins intuitive.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.