Perché GETUTCDATE è precedente a SYSDATETIMEOFFSET?


8

In alternativa, in che modo Microsoft ha reso possibile il viaggio nel tempo?

Considera questo codice:

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;

@Offsetè impostato prima @UTC , ma a volte ha un valore successivo . (L'ho provato su SQL Server 2008 R2 e SQL Server 2016. È necessario eseguirlo alcune volte per rilevare le occorrenze sospette.)

Questo non sembra essere semplicemente una questione di arrotondamento o mancanza di precisione. (In effetti, penso che l'arrotondamento sia ciò che "risolve" il problema di tanto in tanto.) I valori per un'esecuzione di esempio sono i seguenti:

  • Compensare
    • 07/06/2017 12: 01: 58.8801139-05: 00
  • UTC
    • 07-06-2017 17: 01: 58.877
  • UTC dall'offset:
    • 07/06/2017 17: 01: 58.880

Quindi la precisione del datetime consente a .880 di essere un valore valido.

Anche gli esempi GETUTCDATE di Microsoft mostrano che i valori SYS * sono successivi ai metodi precedenti, nonostante siano stati SELEZIONATI in precedenza :

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  
*/

Presumo che ciò avvenga perché provengono da diverse informazioni di sistema sottostanti. Qualcuno può confermare e fornire dettagli?

La documentazione di Microsoft SYSDATETIMEOFFSET afferma che "SQL Server ottiene i valori di data e ora utilizzando l'API Windows GetSystemTimeAsFileTime ()" (grazie srutzky), ma la loro documentazione GETUTCDATE è molto meno specifica, dicendo solo che il "valore è derivato dal sistema operativo del computer su cui è in esecuzione l'istanza di SQL Server ".

(Questo non è del tutto accademico. Ho riscontrato un piccolo problema causato da questo. Stavo aggiornando alcune procedure per utilizzare SYSDATETIMEOFFSET invece di GETUTCDATE, nella speranza di una maggiore precisione in futuro, ma ho iniziato a ottenere ordini strani perché altre procedure erano usando ancora GETUTCDATE e occasionalmente "saltando avanti" delle mie procedure convertite nei registri.)


1
Non so che ci siano altre spiegazioni oltre all'arrotondamento o all'arrotondamento. Quando devi arrotondare qualsiasi valore a una di minore precisione, e quando in alcuni casi devi arrotondare per difetto e altri devi arrotondare (semplici regole matematiche in gioco qui), puoi aspettarti che in alcuni casi il valore arrotondato sarà inferiore o superiore al valore originale e più preciso. Se vuoi assicurarti che il valore meno preciso sia sempre prima (o sempre dopo) il più preciso, puoi sempre manipolarlo di 3 millisecondi usando DATEADD ().
Aaron Bertrand

(Inoltre, perché non modificare TUTTE le procedure contemporaneamente per utilizzare il valore più recente e più preciso?)
Aaron Bertrand

Non penso che possa essere una risposta semplice quanto un arrotondamento perché se converti il ​​valore di datetimeoffset di 2017-06-07 12: 01: 58.8801139 -05: 00 direttamente in datetime, seleziona 2017-06-07 12:01 : 58.880, non 2017-06-07 12: 01: 58.877. Quindi o GETUTCDATE arrotonda internamente in modo diverso o sono fonti diverse.
Riley Major,

Preferirei farle in modo incrementale per cambiare il minor numero possibile di cose alla volta. scribnasium.com/2017/05/deploying-the-old-fashioned-way Alla fine li farò in un batch o vivrò con l'incoerenza nei registri.
Riley Major,

2
Inoltre, vorrei aggiungere che il viaggio nel tempo è sempre possibile nei computer . Non dovresti mai programmare aspettandoti che il tempo passi sempre avanti. Il motivo è la deriva e le correzioni all'ora del sistema, principalmente guidate dal servizio ora di Windows , ma anche dalla regolazione dell'utente. La deriva e la correzione di millisecondi sono effettivamente riscontrate molto spesso.
Remus Rusanu l'

Risposte:


9

Il problema è una combinazione di granularità / accuratezza del tipo di dati e fonte dei valori.

Innanzitutto, DATETIMEè preciso / granulare solo ogni 3 millisecondi. Quindi, convertendo da un tipo di dati più preciso come DATETIMEOFFSETo DATETIME2non solo arrotondando per eccesso o per difetto al millisecondo più vicino, potrebbe essere diverso di 2 millisecondi.

In secondo luogo, la documentazione sembra implicare una differenza nella provenienza dei valori. Le funzioni SYS * utilizzano le funzioni FileTime ad alta precisione.

La documentazione di SYSDATETIMEOFFSET afferma:

SQL Server ottiene i valori di data e ora utilizzando l'API Windows GetSystemTimeAsFileTime ().

mentre la documentazione di GETUTCDATE afferma:

Questo valore deriva dal sistema operativo del computer su cui è in esecuzione l'istanza di SQL Server.

Quindi, nella documentazione About Time , un grafico mostra i seguenti due (di diversi) tipi:

  • System Time = "Anno, mese, giorno, ora, secondo e millisecondi, presi dall'orologio hardware interno."
  • File Time = "Il numero di intervalli di 100 nanosecondi dal 1 ° gennaio 1601."

Ulteriori indizi si trovano nella documentazione .NET per la StopWatchclasse (enfasi in grassetto corsivo mio):

  • Classe cronometro

    Il cronometro misura il tempo trascorso contando i tick del timer nel meccanismo del timer sottostante. Se l'hardware e il sistema operativo installati supportano un contatore delle prestazioni ad alta risoluzione, la classe Stopwatch utilizza quel contatore per misurare il tempo trascorso. Altrimenti, la classe Cronometro utilizza il timer di sistema per misurare il tempo trascorso.

  • Campo Stopwatch.IsHighResolution

    Il timer utilizzato dalla classe Stopwatch dipende dall'hardware del sistema e dal sistema operativo. IsHighResolution è vero se il cronometro si basa su un contatore delle prestazioni ad alta risoluzione. Altrimenti, IsHighResolution è falso , il che indica che il timer del cronometro si basa sul timer di sistema .

Quindi, ci sono diversi "tipi" di volte che hanno sia precisioni diverse che fonti diverse.

Ma, anche se questa è una logica molto ampia, testare entrambi i tipi di funzioni come fonti per il DATETIMEvalore lo dimostra. Il seguente adattamento della query dalla domanda mostra questo comportamento:

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;

Ritorna:

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

Come puoi vedere nei risultati precedenti, UTC e UTC2 sono entrambi DATETIMEtipi di dati. @UTC2viene impostato tramite SYSUTCDATETIME()e impostato dopo @Offset(anche preso da una funzione SYS *), ma prima del @UTCquale viene impostato tramite GETUTCDATE(). Tuttavia, @UTC2sembra venire prima @UTC. La parte OFFSET di questo è completamente estranea a qualsiasi cosa.

TUTTAVIA, per essere onesti, questa non è ancora una prova in senso stretto. @MartinSmith ha rintracciato la GETUTCDATE()chiamata e ha trovato quanto segue:

inserisci qui la descrizione dell'immagine

Vedo tre cose interessanti in questo stack di chiamate:

  1. Is inizia con una chiamata alla GetSystemTime()quale restituisce un valore preciso solo fino ai millisecondi.
  2. Utilizza DateFromParts (cioè nessuna formula da convertire, basta usare i singoli pezzi).
  3. C'è una chiamata a QueryPerformanceCounter verso il basso. Questo è un contatore delle differenze ad alta risoluzione che potrebbe essere utilizzato come mezzo di "correzione". Ho visto qualche suggerimento sull'uso di un tipo per regolare l'altro nel tempo man mano che intervalli lunghi si allontanano lentamente dalla sincronizzazione (vedere Come ottenere il timestamp della precisione del tick in .NET / C #? Su SO). Questo non viene visualizzato quando si chiama SYSDATETIMEOFFSET.

Qui ci sono molte buone informazioni sui diversi tipi di tempo, diverse fonti, deriva, ecc: acquisizione di timestamp ad alta risoluzione .

Davvero, non è appropriato confrontare i valori di diverse precisioni. Ad esempio, se lo hai 2017-06-07 12:01:58.8770011e 2017-06-07 12:01:58.877, come fai a sapere che quello con meno precisione è maggiore, minore di o uguale al valore con maggiore precisione? Il loro confronto presuppone che quello meno preciso sia effettivamente 2017-06-07 12:01:58.8770000, ma chissà se è vero o no? Il tempo reale avrebbe potuto essere 2017-06-07 12:01:58.8770005o 2017-06-07 12:01:58.8770111.

Tuttavia, se si dispone di DATETIMEtipi di dati, è necessario utilizzare le funzioni SYS * come origine poiché sono più accurate, anche se si perde una certa precisione poiché il valore viene forzato in un tipo meno preciso. E lungo queste linee, sembra avere più senso usare SYSUTCDATETIME()piuttosto che chiamare SYSDATETIMEOFFSET()solo per regolarlo tramite SWITCHOFFSET(@Offset, 0).


Sul mio sistema GETUTCDATEsembra chiamare GetSystemTimee SYSDATETIMEOFFSETchiamare GetSystemTimeAsFileTimesebbene entrambi apparentemente si riducano
Martin Smith

1
@MartinSmith (1/2) Ho trascorso un po 'di tempo su questo annuncio oggi sono fuori tempo per fare di più. Non ho un modo semplice per testare l'API Win C ++ che viene chiamata ed è citata nel blog che hai citato. Posso convertire avanti e indietro tra FileTime e DateTime in .NET, ma DateTime non è un SystemTime in quanto può gestire la massima precisione, ma era senza perdita. Non riesco a dimostrare / confutare quelle GetSystemTimechiamate GetSystemTimeAsFileTime , ma ho notato cose interessanti nello stack di chiamate che hai pubblicato nel commento alla domanda: 1) utilizza DateFromParts quindi molto probabilmente troncato in ms (cont)
Solomon Rutzky l'

@MartinSmith (2/2) anziché arrotondato (poiché SystemTime scende solo a millisecondi) e 2) c'è una chiamata a QueryPerformanceCounter verso il basso. Questo è un contatore delle differenze ad alta risoluzione che potrebbe essere utilizzato come mezzo di "correzione". Ho visto qualche suggerimento sull'uso di un tipo per regolare l'altro nel tempo man mano che lunghi intervalli lentamente si sincronizzano. Ci sono molte buone informazioni qui: acquisizione di timestamp ad alta risoluzione . Se iniziano nello stesso momento, la perdita è da 1 dei 2 passaggi precedenti.
Solomon Rutzky,

Ho pensato che Offset fosse un'aringa rossa ma non ho creato un test più astratto. Avrei dovuto vedere una volta che i documenti Microsoft mostravano la discrepanza. Grazie per averlo confermato.
Riley Major,

La mia sfida è che ho più proc che lo fanno. Ora usano tutti GETUTCDATE. Se cambio alcune delle funzioni SYS * (con Offset o UTC direttamente), iniziano a non funzionare con le altre utilizzando le funzioni GET *. Ma posso conviverci o cambiarli tutti in una volta. Non è un grosso problema. Ha appena suscitato la mia curiosità.
Riley Major,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.