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 la transaction début de temps en tant que SysEndTime
. Les nouvelles valeurs de la table actuelle auront la transaction le temps de commencer comme SysStartTime
.
SysStartTime
et SysEndTime
sont datetime2
colonnes utilisées par des tables temporelles pour enregistrer lorsqu'une ligne était la version actuelle. Le temps de début de la transaction est le moment où la transaction contenant les mises à jour a commencé.
Bol dit:
Les temps enregistrés dans les colonnes System DateTime2 sont basées sur le temps de début de la transaction elle-même. Par exemple, toutes les lignes insérées dans une seule transaction auront la même heure UTC enregistrée dans la colonne correspondant au début de la période System_Time.
Exemple: Je commence à mettre à jour toutes les lignes de ma table de commande à 20160707 11:00:00
Et la transaction prend 5 minutes à courir. Cela crée une rangée dans la table d'histoire pour chaque ligne avec SysEndTime
comme 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 que la mise à jour est en cours d'exécution), ils verraient les anciennes valeurs (en supposant que le niveau d'isolement indiqué par défaut).
Mais si quelqu'un devait ensuite utiliser le AS OF
Syntaxe pour interroger la table temporelle telle qu'elle était à 20160707 11:01:00
ils verraient les nouvelles valeurs car leur SysStartTime
serait 20160707 11:00:00
.
Pour moi, cela signifie que cela ne montre pas ces lignes comme elles étaient à cette époque. S'il utilisait l'heure de fin de la transaction, le problème n'existerait pas.
Questions: Est-ce par conception? Est-ce que je manque quelque chose?
La seule raison pour laquelle je puisse penser que l'utilisation de la transaction commence est que c'est le seul "connu" lorsque la transaction commence. Il ne sait pas lorsque la transaction se terminera lorsqu'elle commence et il faudrait du temps d'appliquer l'heure de fin à la fin qui invaliderait la fin de l'heure de la fin. Est-ce que ça a du sens?
Ceci devrait vous permettre de recréer le problème.
L'idée est de suivre le temps logique vs temps physique. Logical fait simplement référence à ce qu'est un utilisateur/application attend l'heure d'une insertion/mise à jour/Supprimer. Le fait que l'opération DML puisse prendre un certain temps pour une raison quelconque, n'est pas significative ni même facilement déterminée et comprise par un utilisateur. Si vous avez déjà eu dû expliquer la conflit de verrouillage VS à un comptable (j'ai), c'est une situation comparable.
Par exemple, lorsque Bob "raconte" 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 tout le salaire soit maintenant calculé à 42 $/min à partir de cette époque. Bob ne se soucie pas que, pour que cela soit effectué, l'application doit faire 2 lectures et 6 écrit sur la base de données par employé et leurs fichiers de journaux de données + sur un tas de lecteurs RAID-5 SATA II afin qu'il faut environ 7 minutes environ 7 minutes. Pour terminer la tâche pour les 256 employés de Bob. Bob, son comptable et le responsable de la masse salariale se soucient que tous ses employés reçoivent 42 $/min de départ 20160707 11:00:00
. Sinon, les employés mis à jour à 20160707 11:00:01
sera légèrement ennuyé alors que ceux dont les enregistrements ont été mis à jour à 20160707 11:00:07
rassemblera à l'extérieur du département de la paie.
Il existe des cas d'utilisation valide pour suivre le temps physique tel que le débogage et la criminalistique, mais à l'utilisateur final, il n'a généralement pas de sens. Le TLOG maintient à la fois des informations de commande et de chronométrage pour chacune des opérations d'écriture (entre autres), donc c'est là que vous savez comment regarder.
Je crois que c'est en effet une faille de conception, bien qu'on ne soit pas spécifique à SQL Server 2016, car toutes les autres implémentations existantes de tables temporelles (autant que je sache) ont la même faille. Les problèmes qui peuvent survenir avec des tables temporelles à cause de cela sont assez graves; Le scénario de votre exemple est léger par rapport à ce qui peut se tromper en général:
Références de clés étrangères cassées: Supposons que nous ayons deux tables temporelles, avec la table A ayant une référence de clé étrangère à la table B. Disons maintenant que nous avons deux transactions, qui fonctionnent tous les deux à un niveau d'isolement enregistré: transaction 1 commence avant la transaction 2, la transaction 2 insère une rangée dans le tableau B et les engagements, puis la transaction 1 insère une rangée dans le tableau A avec une référence à la nouvelle ligne de B. Depuis l'addition de la nouvelle rangée à B était déjà commise, le La contrainte de clé étrangère est satisfaite et la transaction 1 est capable de s'engager avec succès. Toutefois, si nous devions voir la base de données "à partir" un peu de temps entre la transaction 1 a commencé et lorsque la transaction 2 a commencé, nous verrions le tableau A avec une référence à une rangée de B qui n'existe pas. Donc, dans ce cas, La table temporelle fournit une vue incompatible de la base de données. Bien sûr, cela n'était pas l'intention de la norme SQL: 2011, qui déclare,
Système historique des lignes dans un tableau-Version-Table formulaire de tableau immuable instantané du passé. Toute contrainte qui était en vigueur lorsqu'une ligne de système historique a été créée aurait déjà été vérifiée lorsque cette ligne était une ligne de système actuelle, il n'est donc jamais nécessaire d'appliquer des contraintes sur des lignes de système historiques.
Touches primaires non uniques: Disons que nous avons une table avec une clé primaire et deux transactions, à la fois à un niveau d'isolement enregistré, dans lequel ce qui suit se produit: après la transaction 1 commence mais avant qu'il ne touche pas cela Tableau, transaction 2 Supprime une certaine rangée de la table et commet. Ensuite, la transaction 1 insère une nouvelle ligne avec la même clé primaire que celle supprimée. Cela passe bien, mais lorsque vous regardez la table à partir d'une heure entre la transaction 1 a commencé et lorsque la transaction 2 a commencé, nous verrons deux rangées avec la même clé primaire.
Erreurs sur les mises à jour simultanées: Disons que nous avons une table et deux transactions qui mettent à jour la même ligne de la même ligne, à nouveau à un niveau d'isolement enregistré. La transaction 1 commence en premier, mais la transaction 2 est la première à mettre à jour la ligne. La transaction 2 commence alors et la transaction 1 fait ensuite une mise à jour différente de la rangée et s'engage. Tout cela est bien, sauf que s'il s'agit d'une table temporelle, lors de l'exécution de la mise à jour de 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 depuis que le Sysendtime serait avant le SysStarttime. Dans ce cas, SQL Server jette une erreur et roulez la transaction (par exemple, voir cette discussion ). C'est très désagréable, car au niveau de l'isolement de la lecture ne serait pas prévu que les problèmes de concurrence entraîneraient des échecs novices, ce qui signifie que les applications ne seront pas nécessairement prêtes à être préparées à des tentatives de réessayes. 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 version-version du système sur des tables qui bénéficieront de la version de la version. ( link )
D'autres implémentations de tables temporelles ont traitée ce scénario (deux transactions simultanées à la mise à jour de la même ligne) en offrant une option "ajuster" automatiquement les horodatages si elles sont invalides (voir ici et ici ). Il s'agit d'une solution de contournement laidée, car elle a une conséquence malheureuse de casser l'atomicité des transactions, car d'autres déclarations dans les mêmes transactions ne seront généralement pas ajustées de manière ajustée de la même manière; I.E., avec cette solution de contournement, si nous considérons la base de données "à partir de" certains points à temps, nous pouvons voir des transactions partiellement exécutées.
Solution: Vous avez déjà suggéré la solution évidente, qui est pour la mise en œuvre d'utiliser l'heure de fin de la transaction (c'est-à-dire le temps de validation) au lieu de l'heure de début. Oui, il est vrai que lorsque nous exécutons une déclaration au milieu d'une transaction, il est impossible de savoir ce que le temps de validation sera (tel qu'il est à l'avenir, ou pourrait même n'existera même pas si la transaction devait être roulée. arrière). Mais cela ne signifie pas que la solution n'est pas immentable; Il doit juste être fait de manière différente. Par exemple, lors de l'exécution d'une instruction de mise à jour ou de suppression, dans la création de la ligne d'historique, le système pourrait simplement mettre dans 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 commission de la transaction. . Il n'est pas nécessaire d'entrer dans une régression infinie d'enregistrement de l'époque que l'horodatage était rempli ou quoi que ce soit comme ça.
Dans le contexte de ce type de mise en œuvre, je suggérerais qu'avant la transaction commis, les lignes qu'il ajoute à la table d'historique ne doit pas être visible par l'utilisateur. Du point de vue de l'utilisateur, il devrait simplement apparaître que ces lignes sont ajoutées (avec l'horodatage commettre) au moment de la commission. En particulier, si la transaction ne s'engage jamais avec succès, elle ne devrait jamais apparaître dans l'histoire. Bien entendu, cela est incompatible avec la norme SQL: 2011 décrivant les insertions à l'historique (y compris les horodatages) comme se produisant au moment de la mise à jour et de la suppression des déclarations (par opposition à l'heure du commit). Mais je ne pense pas que cela compte vraiment, considérant que la norme n'a jamais été correctement mise en œuvre (et ne peut jamais être sans danger) en raison des problèmes décrits ci-dessus, ce qui ne semble pas être adressé nulle part dans la norme.
D'un point de vue de la performance, il peut sembler indésirable que le système ait à revenir en arrière et à revoir les lignes d'histoire pour remplir l'horodatage commettre. Mais en fonction de la façon dont cela est fait, le coût pourrait être assez faible. Je ne connais pas vraiment la manière dont SQL Server fonctionne en interne, mais PostgreSQL, par exemple, utilise un journal écriture-avions, ce qui en fait de sorte que si plusieurs mises à jour soient effectuées sur les mêmes parties d'une table, ces mises à jour sont consolidées de manière à ce que les mises à jour soient consolidées Les données doivent seulement être écrites une fois sur les pages de la table physique - et qui s'appliqueraient généralement dans ce scénario. En tout état de cause, il semble qu'un petit prix de payer pour avoir des tables temporelles pouvant préserver la cohérence de la base de données et l'atomicité de la transaction, ainsi que des transactions simultanées sans rupture - lorsque nous considérons qu'avec les implémentations existantes, le système ne peut jamais assurer la cohérence et vous devez Choisissez entre atomicité et concurrence (fiable).
Bien sûr, depuis (autant que je sache) ce type de système n'a jamais été mis en œuvre, je ne peux pas dire avec certitude que cela fonctionnerait - il y a peut-être quelque chose qui me manque - mais je ne vois aucune raison pourquoi cela ne pouvait pas fonctionner.
Pour le moment, vous engagez votre transaction, toutes les données doivent être écrites dans les pages de données (en mémoire et sur le disque dans le fichier journal). Y compris SysStartTime
et SysEndTime
colonnes. Comment connaître l'heure de fin de 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 de la transaction est la seule option, même si cela pourrait être moins intuitif.