Pourquoi GETUTCDATE est-il antérieur à SYSDATETIMEOFFSET?

8

Sinon, comment Microsoft a-t-il rendu possible le voyage dans le temps?

Considérez ce code:

DECLARE @Offset datetimeoffset = sysdatetimeoffset();
DECLARE @UTC datetime = getUTCdate();
DECLARE @UTCFromOffset datetime = CONVERT(datetime,SWITCHOFFSET(@Offset,0));
SELECT
    Offset = @Offset,
    UTC = @UTC,
    UTCFromOffset = @UTCFromOffset,
    TimeTravelPossible = CASE WHEN @UTC < @UTCFromOffset THEN 1 ELSE 0 END;

@Offsetest défini avant @UTC , mais il a parfois une valeur ultérieure . (J'ai essayé cela sur SQL Server 2008 R2 et SQL Server 2016. Vous devez l'exécuter plusieurs fois pour détecter les occurrences suspectes.)

Cela ne semble pas être simplement une question d'arrondi ou de manque de précision. (En fait, je pense que l'arrondi est ce qui "résout" le problème de temps en temps.) Les valeurs pour un exemple d'exécution sont les suivantes:

  • Décalage
    • 2017-06-07 12: 01: 58.8801139 -05: 00
  • UTC
    • 2017-06-07 17: 01: 58.877
  • UTC à partir du décalage:
    • 2017-06-07 17: 01: 58.880

La précision datetime autorise donc le .880 comme valeur valide.

Même les exemples GETUTCDATE de Microsoft montrent que les valeurs SYS * sont plus récentes que les anciennes méthodes, bien qu'elles aient été sélectionnées plus tôt :

SELECT 'SYSDATETIME()      ', SYSDATETIME();  
SELECT 'SYSDATETIMEOFFSET()', SYSDATETIMEOFFSET();  
SELECT 'SYSUTCDATETIME()   ', SYSUTCDATETIME();  
SELECT 'CURRENT_TIMESTAMP  ', CURRENT_TIMESTAMP;  
SELECT 'GETDATE()          ', GETDATE();  
SELECT 'GETUTCDATE()       ', GETUTCDATE();  
/* Returned:  
SYSDATETIME()            2007-05-03 18:34:11.9351421  
SYSDATETIMEOFFSET()      2007-05-03 18:34:11.9351421 -07:00  
SYSUTCDATETIME()         2007-05-04 01:34:11.9351421  
CURRENT_TIMESTAMP        2007-05-03 18:34:11.933  
GETDATE()                2007-05-03 18:34:11.933  
GETUTCDATE()             2007-05-04 01:34:11.933  
*/

Je suppose que c'est parce qu'ils proviennent de différentes informations système sous-jacentes. Quelqu'un peut-il confirmer et fournir des détails?

La documentation SYSDATETIMEOFFSET de Microsoft indique que "SQL Server obtient les valeurs de date et d'heure en utilisant l'API Windows GetSystemTimeAsFileTime ()" (merci srutzky), mais leur documentation GETUTCDATE est beaucoup moins spécifique, indiquant seulement que la "valeur est dérivée du système d'exploitation de la ordinateur sur lequel s'exécute l'instance de SQL Server ".

(Ce n'est pas entièrement académique. J'ai rencontré un problème mineur causé par cela. J'étais en train de mettre à niveau certaines procédures pour utiliser SYSDATETIMEOFFSET au lieu de GETUTCDATE, dans l'espoir d'une plus grande précision à l'avenir, mais j'ai commencé à obtenir des commandes étranges parce que d'autres procédures étaient toujours en utilisant GETUTCDATE et parfois "sauter en avant" de mes procédures converties dans les journaux.)

Riley Major
la source
1
Je ne sais pas s'il y a d'autre explication que d'arrondir ou d'arrondir. Lorsque vous devez arrondir une valeur avec une précision moindre, et lorsque dans certains cas vous devez arrondir, et d'autres vous devez arrondir (règles mathématiques simples en jeu ici), vous pouvez vous attendre à ce que dans certains cas, la valeur arrondie sera soit inférieure, soit supérieure à la valeur d'origine, plus précise. Si vous voulez vous assurer que la valeur la moins précise est toujours avant (ou toujours après) la plus précise, vous pouvez toujours la manipuler de 3 millisecondes à l'aide de DATEADD ().
Aaron Bertrand
(Aussi, pourquoi ne pas simplement changer TOUTES les procédures en même temps pour utiliser la nouvelle valeur plus précise?)
Aaron Bertrand
Je ne pense pas que cela puisse être une réponse aussi simple que l'arrondi, car si vous convertissez la valeur datetimeoffset de 2017-06-07 12: 01: 58.8801139 -05: 00 directement en datetime, il sélectionne 2017-06-07 12:01 : 58.880, pas 2017-06-07 12: 01: 58.877. Donc, GETUTCDATE arrondit en interne différemment ou ce sont des sources différentes.
Riley Major
Je préfère les faire progressivement pour changer le moins de choses possible à la fois. scribnasium.com/2017/05/deploying-the-old-fashioned-way Je vais éventuellement les faire en lot ou vivre avec l'incohérence dans les journaux.
Riley Major
2
De plus je voudrais ajouter que le voyage dans le temps est toujours possible dans les ordinateurs . Vous ne devriez jamais coder en espérant que le temps avance toujours. La raison en est la dérive et les corrections de l'heure système, principalement provoquées par le service de temps Windows , mais aussi par l'ajustement de l'utilisateur. La dérive et la correction en millisecondes sont en fait très souvent rencontrées.
Remus Rusanu

Réponses:

9

Le problème est une combinaison de granularité / précision du type de données et de la source des valeurs.

Tout d'abord, il DATETIMEn'est précis / granulaire que toutes les 3 millisecondes. Par conséquent, la conversion à partir d'un type de données plus précis, tel que DATETIMEOFFSETou DATETIME2ne sera pas simplement arrondi à la milliseconde la plus proche, pourrait être différente de 2 millisecondes.

Deuxièmement, la documentation semble impliquer une différence dans la provenance des valeurs. Les fonctions SYS * utilisent les fonctions FileTime de haute précision.

La documentation SYSDATETIMEOFFSET indique:

SQL Server obtient les valeurs de date et d'heure à l'aide de l'API Windows GetSystemTimeAsFileTime ().

tandis que la documentation GETUTCDATE indique:

Cette valeur est dérivée du système d'exploitation de l'ordinateur sur lequel s'exécute l'instance de SQL Server.

Ensuite, dans la documentation À propos de l'heure , un graphique présente les deux (parmi plusieurs) types suivants:

  • Heure système = "Année, mois, jour, heure, seconde et milliseconde, extraits de l'horloge matérielle interne."
  • File Time = "Le nombre d'intervalles de 100 nanosecondes depuis le 1er janvier 1601."

Des indices supplémentaires sont dans la documentation .NET pour la StopWatchclasse (accentuation en gras italique le mien):

  • Classe chronomètre

    Le chronomètre mesure le temps écoulé en comptant les tics du minuteur dans le mécanisme de minuteur sous-jacent. Si le matériel et le système d'exploitation installés prennent en charge un compteur de performances haute résolution, la classe Chronomètre utilise ce compteur pour mesurer le temps écoulé. Sinon, la classe Chronomètre utilise le minuteur système pour mesurer le temps écoulé.

  • Chronomètre.IsHighResolution Field

    Le minuteur utilisé par la classe Chronomètre dépend du matériel système et du système d'exploitation. IsHighResolution est vrai si le chronomètre est basé sur un compteur de performances haute résolution. Sinon, IsHighResolution est false , ce qui indique que le chronomètre est basé sur le chronomètre système .

Par conséquent, il existe différents «types» de temps qui ont à la fois des précisions et des sources différentes.

Mais, même si c'est une logique très lâche, tester les deux types de fonctions comme sources de la DATETIMEvaleur le prouve. L'adaptation suivante de la requête de la question montre ce comportement:

DECLARE @Offset DATETIMEOFFSET = SYSDATETIMEOFFSET(),
        @UTC2 DATETIME = SYSUTCDATETIME(),
        @UTC DATETIME = GETUTCDATE();
DECLARE @UTCFromOffset DATETIME = CONVERT(DATETIME, SWITCHOFFSET(@Offset, 0));
SELECT
    Offset = @Offset,
    UTC2 = @UTC2,
    UTC = @UTC,
    UTCFromOffset = @UTCFromOffset,
    TimeTravelPossible = CASE WHEN @UTC < @UTCFromOffset THEN 1 ELSE 0 END,
    TimeTravelPossible2 = CASE WHEN @UTC2 < @UTCFromOffset THEN 1 ELSE 0 END;

Retour:

Offset                 2017-06-07 17:50:49.6729691 -04:00
UTC2                   2017-06-07 21:50:49.673
UTC                    2017-06-07 21:50:49.670
UTCFromOffset          2017-06-07 21:50:49.673
TimeTravelPossible     1
TimeTravelPossible2    0

Comme vous pouvez le voir dans les résultats ci-dessus, UTC et UTC2 sont tous deux des DATETIMEtypes de données. @UTC2est défini via SYSUTCDATETIME()et est défini après @Offset(également tiré d'une fonction SYS *), mais avant @UTClequel est défini via GETUTCDATE(). Pourtant, @UTC2semble venir avant @UTC. La partie OFFSET de tout cela n'a aucun rapport avec quoi que ce soit.

CEPENDANT, pour être juste, ce n'est toujours pas une preuve au sens strict. @MartinSmith a retracé l' GETUTCDATE()appel et a trouvé ce qui suit:

entrez la description de l'image ici

Je vois trois choses intéressantes dans cette pile d'appels:

  1. Is commence par un appel GetSystemTime()auquel renvoie une valeur qui n'est précise que jusqu'aux millisecondes.
  2. Il utilise DateFromParts (c'est-à-dire aucune formule à convertir, utilisez simplement les pièces individuelles).
  3. Il y a un appel à QueryPerformanceCounter vers le bas. Il s'agit d'un compteur de différence à haute résolution qui pourrait être utilisé comme moyen de "correction". J'ai vu une suggestion d'utiliser un type pour ajuster l'autre au fil du temps car de longs intervalles se désynchronisent lentement (voir Comment obtenir l'horodatage de la précision des ticks dans .NET / C #? Sur SO). Cela n'apparaît pas lors de l'appel SYSDATETIMEOFFSET.

Il y a beaucoup de bonnes informations ici concernant les différents types de temps, les différentes sources, la dérive, etc.: Acquérir des horodatages haute résolution .

En réalité, il n'est pas approprié de comparer les valeurs de différentes précisions. Par exemple, si vous avez 2017-06-07 12:01:58.8770011et 2017-06-07 12:01:58.877, alors comment savez-vous que celui avec moins de précision est supérieur, inférieur ou égal à la valeur avec plus de précision? Les comparer suppose que le moins précis est en fait 2017-06-07 12:01:58.8770000, mais qui sait si c'est vrai ou non? Le temps réel aurait pu être soit 2017-06-07 12:01:58.8770005ou 2017-06-07 12:01:58.8770111.

Cependant, si vous avez des DATETIMEtypes de données, vous devez utiliser les fonctions SYS * comme source car elles sont plus précises, même si vous perdez une certaine précision car la valeur est forcée dans un type moins précis. Et dans ce sens, il semble plus logique d'utiliser SYSUTCDATETIME()plutôt que d'appeler SYSDATETIMEOFFSET()uniquement pour l'ajuster via SWITCHOFFSET(@Offset, 0).

Solomon Rutzky
la source
Sur mon système GETUTCDATEsemble appeler GetSystemTimeet SYSDATETIMEOFFSETappelle GetSystemTimeAsFileTimebien que les deux se résument apparemment à la même chose blogs.msdn.microsoft.com/oldnewthing/20131101-00/?p=2763
Martin Smith
1
@MartinSmith (1/2) J'ai passé un peu de temps sur cette annonce, je n'ai plus de temps aujourd'hui pour en faire plus. Je n'ai pas de moyen facile de tester l'API C ++ Win qui est appelée et référencée dans le blog que vous avez mentionné. Je peux convertir dans les deux sens entre FileTime et DateTime dans .NET, mais DateTime n'est pas un SystemTime car il peut gérer une précision complète, mais il était sans perte. Je ne peux pas prouver / réfuter ces GetSystemTimeappels GetSystemTimeAsFileTime , mais j'ai remarqué des choses intéressantes dans la pile d'appels que vous avez postée dans le commentaire de la question: 1) il utilise DateFromParts donc très probablement tronqué en ms (suite)
Solomon Rutzky
@MartinSmith (2/2) au lieu d'arrondi (puisque SystemTime ne descend qu'en millisecondes), et 2) il y a un appel à QueryPerformanceCounter vers le bas. Il s'agit d'un compteur de différence à haute résolution qui pourrait être utilisé comme moyen de "correction". J'ai vu une suggestion d'utiliser un type pour ajuster l'autre au fil du temps, car de longs intervalles se désynchronisent lentement. Il y a beaucoup de bonnes informations ici: Acquérir des horodatages haute résolution . S'ils commencent en même temps, la perte provient de l'une des 2 étapes ci-dessus.
Solomon Rutzky
Je pensais que Offset était un hareng rouge mais je n'ai pas créé de test plus abstrait. J'aurais dû une fois que j'ai vu les documents Microsoft montrer la différence. Merci de l'avoir confirmé.
Riley Major
Mon défi est que j'ai plusieurs procs faisant cela. Ils utilisent tous maintenant GETUTCDATE. Si je modifie certaines des fonctions SYS * (avec Offset ou UTC directement), elles commencent à se désorganiser avec les autres en utilisant les fonctions GET *. Mais je peux vivre avec ou les changer tous en même temps. Pas une grosse affaire. Cela a simplement éveillé ma curiosité.
Riley Major