Warum ist GETUTCDATE früher als SYSDATETIMEOFFSET?

8

Wie hat Microsoft alternativ Zeitreisen ermöglicht?

Betrachten Sie diesen 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;

@Offsetist vorher eingestellt @UTC , hat aber manchmal einen späteren Wert. (Ich habe dies unter SQL Server 2008 R2 und SQL Server 2016 versucht. Sie müssen es einige Male ausführen, um die verdächtigen Vorkommen zu erkennen.)

Dies scheint nicht nur eine Frage der Rundung oder der Ungenauigkeit zu sein. (Tatsächlich denke ich, dass die Rundung das Problem gelegentlich "behebt".) Die Werte für einen Probelauf lauten wie folgt:

  • Offset
    • 2017-06-07 12: 01: 58.8801139 -05: 00
  • koordinierte Weltzeit
    • 2017-06-07 17: 01: 58.877
  • UTC vom Versatz:
    • 2017-06-07 17: 01: 58.880

Die Datums- / Uhrzeitgenauigkeit erlaubt also die .880 als gültigen Wert.

Selbst die GETUTCDATE-Beispiele von Microsoft zeigen, dass die SYS * -Werte später als die älteren Methoden sind, obwohl sie zuvor ausgewählt wurden :

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

Ich nehme an, das liegt daran, dass sie aus verschiedenen zugrunde liegenden Systeminformationen stammen. Kann jemand Details bestätigen und angeben?

In der SYSDATETIMEOFFSET-Dokumentation von Microsoft heißt es: "SQL Server ermittelt die Datums- und Zeitwerte mithilfe der Windows-API GetSystemTimeAsFileTime ()" (danke srutzky). Die GETUTCDATE-Dokumentation ist jedoch viel weniger spezifisch und besagt nur, dass der Wert vom Betriebssystem des Betriebssystems abgeleitet ist Computer, auf dem die Instanz von SQL Server ausgeführt wird ".

(Dies ist nicht ganz akademisch. Ich bin auf ein kleines Problem gestoßen, das dadurch verursacht wurde. Ich habe einige Verfahren aktualisiert, um SYSDATETIMEOFFSET anstelle von GETUTCDATE zu verwenden, in der Hoffnung auf eine größere Präzision in der Zukunft, aber ich bekam merkwürdige Bestellungen, weil andere Verfahren dies waren Ich benutze immer noch GETUTCDATE und "springe" gelegentlich meinen konvertierten Prozeduren in den Protokollen voraus.)

Riley Major
quelle
1
Ich weiß nicht, dass es eine andere Erklärung gibt als Abrunden oder Aufrunden. Wenn Sie einen Wert auf einen Wert mit geringerer Genauigkeit runden müssen und wenn Sie in einigen Fällen abrunden müssen und in anderen Fällen aufrunden müssen (hier gelten einfache mathematische Regeln), können Sie in einigen Fällen den gerundeten Wert erwarten ist entweder niedriger oder höher als der ursprüngliche, genauere Wert. Wenn Sie sicherstellen möchten, dass der weniger genaue Wert immer vor (oder immer nach) dem genaueren Wert liegt, können Sie ihn mit DATEADD () immer um 3 Millisekunden bearbeiten.
Aaron Bertrand
(Warum nicht einfach ALLE Prozeduren gleichzeitig ändern, um den neueren, genaueren Wert zu verwenden?)
Aaron Bertrand
Ich denke nicht, dass es eine so einfache Antwort wie das Runden sein kann, denn wenn Sie den datetimeoffset-Wert von 2017-06-07 12: 01: 58.8801139 -05: 00 direkt in datetime konvertieren, wird 2017-06-07 12:01 ausgewählt : 58.880, nicht 2017-06-07 12: 01: 58.877. Entweder rundet GETUTCDATE intern unterschiedlich oder es handelt sich um unterschiedliche Quellen.
Riley Major
Ich würde es vorziehen, sie schrittweise zu tun, um so wenig Dinge wie möglich gleichzeitig zu ändern. scribnasium.com/2017/05/deploying-the-old-fashioned-way Ich werde sie schließlich in einem Stapel ausführen oder mit der Inkonsistenz in den Protokollen leben.
Riley Major
2
Außerdem möchte ich hinzufügen, dass Zeitreisen in Computern immer möglich sind . Sie sollten niemals codieren, wenn Sie erwarten, dass die Zeit immer vorwärts geht. Der Grund ist eine Abweichung und Korrekturen der Systemzeit, die hauptsächlich vom Windows-Zeitdienst , aber auch von der Benutzeranpassung abhängen. Millisekunden Drift und Korrektur sind tatsächlich sehr häufig anzutreffen.
Remus Rusanu

Antworten:

9

Das Problem ist eine Kombination aus Granularität / Genauigkeit des Datentyps und Quelle der Werte.

Erstens DATETIMEist nur alle 3 Millisekunden genau / granular. Daher von einem genaueren Datentyp wie die Umwandlung DATETIMEOFFSEToder DATETIME2wird nicht gleich um nach oben oder unten auf die nächste Millisekunde, könnte es 2 Millisekunden unterschiedlich sein.

Zweitens scheint die Dokumentation einen Unterschied darin zu implizieren, woher die Werte stammen. Die SYS * -Funktionen verwenden die hochpräzisen FileTime-Funktionen.

In der Dokumentation zu SYSDATETIMEOFFSET heißt es:

SQL Server ermittelt die Datums- und Uhrzeitwerte mithilfe der Windows-API GetSystemTimeAsFileTime ().

In der GETUTCDATE- Dokumentation heißt es:

Dieser Wert wird vom Betriebssystem des Computers abgeleitet, auf dem die Instanz von SQL Server ausgeführt wird.

In der About Time- Dokumentation zeigt ein Diagramm die folgenden zwei (von mehreren) Typen:

  • Systemzeit = "Jahr, Monat, Tag, Stunde, Sekunde und Millisekunde, entnommen aus der internen Hardware-Uhr."
  • File Time = "Die Anzahl der 100-Nanosekunden-Intervalle seit dem 1. Januar 1601."

Weitere Hinweise finden Sie in der .NET-Dokumentation für die StopWatchKlasse (Hervorhebung in Fettdruck kursiv):

  • Stoppuhr-Klasse

    Die Stoppuhr misst die verstrichene Zeit durch Zählen der Timer-Ticks im zugrunde liegenden Timer-Mechanismus. Wenn die installierte Hardware und das installierte Betriebssystem einen hochauflösenden Leistungsindikator unterstützen, verwendet die Stoppuhrklasse diesen Zähler, um die verstrichene Zeit zu messen. Andernfalls verwendet die Stoppuhrklasse den Systemzeitgeber , um die verstrichene Zeit zu messen.

  • Stopwatch.IsHighResolution Field

    Der von der Stoppuhrklasse verwendete Timer hängt von der Systemhardware und dem Betriebssystem ab. IsHighResolution ist wahr, wenn der Stoppuhr-Timer auf einem hochauflösenden Leistungsindikator basiert. Andernfalls ist IsHighResolution false , was darauf hinweist, dass der Stoppuhr-Timer auf dem System-Timer basiert .

Daher gibt es verschiedene "Arten" von Zeiten, die sowohl unterschiedliche Präzisionen als auch unterschiedliche Quellen haben.

Aber selbst wenn dies eine sehr lockere Logik ist, DATETIMEbeweist das Testen beider Arten von Funktionen als Quellen für den Wert dies. Die folgende Anpassung der Abfrage aus der Frage zeigt dieses Verhalten:

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;

Kehrt zurück:

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

Wie Sie in den obigen Ergebnissen sehen können, sind UTC und UTC2 beide DATETIMEDatentypen. @UTC2wird über gesetzt SYSUTCDATETIME()und wird danach gesetzt @Offset(auch aus einer SYS * -Funktion entnommen), aber davor @UTCwird via gesetzt GETUTCDATE(). Doch @UTC2scheint vorher zu kommen @UTC. Der OFFSET-Teil davon hat nichts mit irgendetwas zu tun.

Um fair zu sein, ist dies jedoch immer noch kein Beweis im engeren Sinne. @MartinSmith verfolgte den GETUTCDATE()Anruf und fand Folgendes:

Geben Sie hier die Bildbeschreibung ein

Ich sehe drei interessante Dinge in diesem Aufrufstapel:

  1. Is beginnt mit einem Aufruf, GetSystemTime()der einen Wert zurückgibt, der nur auf die Millisekunden genau ist.
  2. Es verwendet DateFromParts (dh keine Formel zum Konvertieren, verwenden Sie nur die einzelnen Teile).
  3. Es ist ein Aufruf an Queryperformancecounter nach unten. Dies ist ein hochauflösender Differenzzähler, der als Mittel zur "Korrektur" verwendet werden könnte. Ich habe einige Vorschläge gesehen, einen Typ zu verwenden, um den anderen im Laufe der Zeit anzupassen, da lange Intervalle langsam nicht mehr synchron sind (siehe So erhalten Sie den Zeitstempel für die Tick-Genauigkeit in .NET / C #? Auf SO). Dies wird beim Anruf nicht angezeigt SYSDATETIMEOFFSET.

Hier gibt es viele gute Informationen zu den verschiedenen Arten von Zeit, verschiedenen Quellen, Drift usw .: Erfassen von hochauflösenden Zeitstempeln .

Es ist wirklich nicht angebracht, Werte verschiedener Präzisionen zu vergleichen. Zum Beispiel, wenn Sie haben 2017-06-07 12:01:58.8770011und 2017-06-07 12:01:58.877dann wie Sie wissen, dass das eine mit weniger Präzision größer als, kleiner als oder mit mehr Präzision auf den Wert gleich? Wenn man sie vergleicht, wird davon ausgegangen, dass die weniger genaue tatsächlich ist 2017-06-07 12:01:58.8770000, aber wer weiß, ob das wahr ist oder nicht? Die Echtzeit könnte entweder 2017-06-07 12:01:58.8770005oder gewesen sein 2017-06-07 12:01:58.8770111.

Wenn Sie jedoch DATETIMEDatentypen haben, sollten Sie die SYS * -Funktionen als Quelle verwenden, da diese genauer sind, auch wenn Sie an Genauigkeit verlieren, da der Wert in einen weniger präzisen Typ gezwungen wird. In diesem Sinne scheint es sinnvoller zu sein, zu verwenden, SYSUTCDATETIME()als SYSDATETIMEOFFSET()nur aufzurufen , um es über anzupassen SWITCHOFFSET(@Offset, 0).

Solomon Rutzky
quelle
Auf meinem System GETUTCDATEscheint zu rufen GetSystemTimeund SYSDATETIMEOFFSETAnrufe GetSystemTimeAsFileTimeobwohl beide offenbar auf die gleiche Sache einkochen blogs.msdn.microsoft.com/oldnewthing/20131101-00/?p=2763
Martin Smith
1
@MartinSmith (1/2) Ich habe einige Zeit mit dieser Anzeige verbracht und habe heute keine Zeit mehr, um mehr zu tun. Ich habe keine einfache Möglichkeit, die aufgerufene C ++ Win-API zu testen, auf die in dem von Ihnen erwähnten Blog verwiesen wird. Ich kann in .NET zwischen FileTime und DateTime hin und her konvertieren, aber DateTime ist keine SystemTime, da es die volle Genauigkeit verarbeiten kann, aber es war verlustfrei. Ich kann diese GetSystemTimeAnrufe nicht beweisen / widerlegen GetSystemTimeAsFileTime , aber ich habe interessante Dinge in dem Aufrufstapel bemerkt, den Sie im Fragekommentar gepostet haben: 1) Es verwendet DateFromParts, also höchstwahrscheinlich abgeschnitten in ms (cont)
Solomon Rutzky
@MartinSmith (2/2) statt gerundet (da SystemTime nur auf Millisekunden reduziert wird) und 2) wird QueryPerformanceCounter nach unten aufgerufen . Dies ist ein hochauflösender Differenzzähler, könnte als ein Mittel zur „Korrektur“ verwendet werden wird. Ich habe einige Vorschläge gesehen, einen Typ zu verwenden, um den anderen im Laufe der Zeit anzupassen, da lange Intervalle langsam nicht mehr synchron sind. Hier gibt es viele gute Informationen: Erfassen von hochauflösenden Zeitstempeln . Wenn sie zur gleichen Zeit beginnen, ergibt sich der Verlust aus einem der beiden oben genannten Schritte.
Solomon Rutzky
Ich dachte, Offset sei ein roter Hering, habe aber keinen abstrakteren Test erstellt. Ich hätte es tun sollen, wenn ich gesehen hätte, dass die Microsoft-Dokumente die Diskrepanz zeigen. Vielen Dank für die Bestätigung.
Riley Major
Meine Herausforderung besteht darin, dass ich mehrere Prozesse habe, die dies tun. Sie alle verwenden jetzt GETUTCDATE. Wenn ich einige in eine der SYS * -Funktionen ändere (mit Offset oder UTC direkt), geraten sie mit den anderen mithilfe der GET * -Funktionen außer Betrieb. Aber ich kann entweder damit leben oder sie alle auf einmal ändern. Keine große Sache. Es hat nur meine Neugier geweckt.
Riley Major