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
.
SysStartTime
et SysEndTime
sont des datetime2
colonnes 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:00
et la transaction prend 5 minutes pour s'exécuter. Cela crée une ligne dans la table d'historique pour chaque ligne avec SysEndTime
as 20160707 11:00:00
. Toutes les lignes de la table actuelle auront un SysStartTime
de 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 OF
syntaxe pour interroger la table temporelle telle qu'elle était, 20160707 11:01:00
il verrait les nouvelles valeurs parce que la leur SysStartTime
serait 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.
la source
20160707 11:04:58
et 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:00
et que les lignes ont été renvoyées, mais que plus tardAS OF
ne les affiche pas.Réponses:
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 à partir20160707 11:00:00
. Sinon, les employés qui ont été mis à jour20160707 11:00:01
seront légèrement ennuyés tandis que ceux dont les dossiers ont été mis à jour20160707 11:00:07
se 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.
la source
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,
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:
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.
la source
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
SysStartTime
etSysEndTime
colonnes. 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.
la source