web-dev-qa-db-fra.com

Échec de l'ajout de la période pour SYSTEM_TIME sur la table

J'ai:

  • table avec les données existantes
  • SQL Server 2016 SP1
  • SQL Server Management Studio 17.5

J'utilise la déclaration suivante pour créer une table temporelle:

ALTER TABLE [dbo].[AnalysisCustomRollupsV2JoinGroups]
ADD [SysStartTime] DATETIME2(0) GENERATED ALWAYS AS ROW START HIDDEN CONSTRAINT DF_AnalysisCustomRollupsV2JoinGroups_SysStart DEFAULT GETUTCDATE()  
   ,[SysEndTime] DATETIME2(0) GENERATED ALWAYS AS ROW END HIDDEN CONSTRAINT DF_AnalysisCustomRollupsV2JoinGroups_SysEnd DEFAULT CONVERT(DATETIME2(0), '9999-12-31 23:59:59'),   
PERIOD FOR SYSTEM_TIME ([SysStartTime], [SysEndTime]); 

ALTER TABLE [dbo].[AnalysisCustomRollupsV2JoinGroups]   
SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.AnalysisCustomRollupsV2JoinGroupsChanges));

Le problème:

Sur mon instance SQL locale, j'ai de nombreuses bases de données; il est très étrange que la requête s'exécute avec succès sur certains d'entre eux, et sur certains d'entre eux, cela me donne l'erreur suivante:

Msg 13542, niveau 16, état 0, ligne 51 ADD PERIOD FOR SYSTEM_TIME sur la table 'dbo.AnalysisCustomRollupsV2JoinGroups' a échoué car il existe des enregistrements ouverts dont le début de la période est défini sur une valeur future.

Parfois, lorsque je débogue/exécute la requête, la requête initiale s'exécute avec succès.

J'ai lu que cela pouvait être dû au fait que j'avais des données existantes dans le tableau. J'ai donc changé la logique comme ceci:

  • créer une table tampon et la remplir avec tous les enregistrements
  • supprimer les enregistrements de la table d'origine
  • créer la table temporelle
  • déplacer les enregistrements en arrière et supprimer la table tampon

et encore une fois, sur certaines bases de données, c'est OK, et sur d'autres non. Essayer de résoudre le problème que j'ai trouvé ce qui suit:

Pour la date de début, j'ai spécifié la date UTC actuelle - il peut s'agir de toute date et heure qui ne sont pas dans le futur, mais notez que cela devrait être UTC. Si j'avais essayé d'utiliser GETDATE, comme je suis actuellement sur l'heure d'été britannique, j'obtiendrais l'erreur suivante: Msg 13542, niveau 16, état 0, ligne 51 ADD PERIOD FOR SYSTEM_TIME sur la table 'TestAudit.dbo.SomeData' a échoué car il existe des enregistrements ouverts avec le début de la période défini sur une valeur future.

Que signifie ce qui précède? Je dois changer l'heure de la machine? Ou parce que ma machine locale n'est pas à l'heure UTC, j'obtiens parfois cette erreur?

7
gotqn

Je pense avoir trouvé comment résoudre mon problème, mais je ne vais pas accepter cette réponse car je ne suis pas en mesure d'expliquer la cause du problème et de garantir que cela fonctionnera à tout moment. C'est un correctif trouvé après de nombreux tests et je serai heureux si quelqu'un peut apporter plus de lumière ici.


Je n'ai jamais utilisé datetime2 Avec précision. Donc, je suis retourné à la source de ce format datetime2(0) - Modifier la table non temporelle pour être une table temporelle versionnée par le système . La seule différence avec le script que j'utilisais était la fonction date-heure. J'ai utilisé GETUTCDATE() car je n'ai pas besoin d'être aussi précis avec datetime(0) (2018-03-15 07:21:02 Par exemple) et dans l'exemple c'est SYSUTCDATETIME(). Donc, je l'ai changé.

J'ai créé un script qui supprime une base de données si elle existe, restaure une base de données à partir d'une sauvegarde, puis exécute mon code en boucle (comme je l'ai dit, j'obtiens parfois l'erreur et il était très difficile de la reproduire).

J'ai exécuté un script plusieurs fois et j'obtenais un nombre différent d'échecs (parfois 70%, parfois 50%, parfois ci-dessous):

enter image description here

J'ai trouvé ceci Pourquoi GETUTCDATE est-il plus tôt que SYSDATETIMEOFFSET? discussion sur les différences entre les anciennes et les nouvelles fonctions date-heure. Créez ensuite la requête suivante:

DECLARE @UTC DATETIME2(0) = GETUTCDATE();
DECLARE @SYSUTC DATETIME2(0) = SYSUTCDATETIME();

WHILE DATEPART(SECOND, @UTC) = DATEPART(SECOND, @SYSUTC)
BEGIN;
    SET @UTC  = GETUTCDATE();
    SET @SYSUTC  = SYSUTCDATETIME();
END;

SELECT @UTC AS [UTC]
      ,@SYSUTC AS [SYS UTC]
      ,DATEPART(SECOND, @UTC) AS [UTC sec]
      ,DATEPART(SECOND, @SYSUTC) AS [SYS UTC sec]
      ,CASE WHEN @UTC < @SYSUTC THEN 1 ELSE 0 END AS [TimeTravelPossible]
      ,CONVERT(DATETIME2(0), @UTC) AS [UTC date]
      ,CONVERT(DATETIME2(0), @SYSUTC) AS [SYS UTC date]
      ,IIF(CONVERT(DATETIME2(0), @UTC) = CONVERT(DATETIME2(0), @SYSUTC), 1, 0) AS [Are The Same];

Je voulais juste vérifier si je peux obtenir différentes dates datetime2(0) en utilisant sys et non la fonction sys date time. Et bien sûr, c'est possible.

enter image description here

Peut-être que les vérifications effectuées par le moteur font quelque chose comme ça, en comparant sa date actuelle avec ma date la plus récente et cela provoque l'erreur - open records with start of period set to a value in the future.

J'ai changé le script comme ceci et exécuté 1 000 fois la nuit dernière - aucune erreur n'a été générée. Donc, je crois que j'ai résolu ce problème particulier, mais je ne peux pas en être sûr.

ALTER TABLE [dbo].[AnalysisCustomRollupsV2JoinGroups]
ADD [SysStartTime] DATETIME2 GENERATED ALWAYS AS ROW START HIDDEN CONSTRAINT DF_AnalysisCustomRollupsV2JoinGroups_SysStart DEFAULT SYSUTCDATETIME()  
   ,[SysEndTime] DATETIME2 GENERATED ALWAYS AS ROW END HIDDEN CONSTRAINT DF_AnalysisCustomRollupsV2JoinGroups_SysEnd DEFAULT CONVERT(DATETIME2, '9999-12-31 23:59:59.9999999'),   
PERIOD FOR SYSTEM_TIME ([SysStartTime], [SysEndTime]); 

ALTER TABLE [dbo].[AnalysisCustomRollupsV2JoinGroups]   
SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.AnalysisCustomRollupsV2JoinGroupsChanges));
4
gotqn