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.
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.