为什么 GETUTCDATE 早于 SYSDATETIMEOFFSET?

Ril*_*jor 8 sql-server datetime

或者,微软是如何让时间旅行成为可能的?

考虑这个代码:

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;
Run Code Online (Sandbox Code Playgroud)

@Offset设置在 之前 @UTC,但它有时具有较晚的值。(我已经在 SQL Server 2008 R2 和 SQL Server 2016 上尝试过这个。你必须运行几次才能捕捉到可疑的事件。)

这似乎不仅仅是四舍五入或缺乏精度的问题。(事实上​​,我认为舍入是偶尔“修复”问题的原因。)示例运行的值如下:

  • 抵消
    • 2017-06-07 12:01:58.8801139-05:00
  • 世界标准时间
    • 2017-06-07 17:01:58.877
  • UTC 偏移量:
    • 2017-06-07 17:01:58.880

因此日期时间精度允许 .880 作为有效值。

甚至Microsoft 的 GETUTCDATE 示例也显示 SYS* 值于旧方法,尽管 SELECTed较早

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  
*/
Run Code Online (Sandbox Code Playgroud)

我认为这是因为它们来自不同的底层系统信息。谁能确认并提供详细信息?

Microsoft 的 SYSDATETIMEOFFSET 文档说“SQL Server 通过使用 GetSystemTimeAsFileTime() Windows API 获取日期和时间值”(感谢 srutzky),但他们的GETUTCDATE 文档不太具体,只说“值来自于运行 SQL Server 实例的计算机”。

(这不完全是学术性的。我遇到了一个由此引起的小问题。我正在升级一些程序以使用 SYSDATETIMEOFFSET 而不是 GETUTCDATE,希望在未来获得更高的精度,但我开始得到奇怪的排序,因为其他程序是仍在使用 GETUTCDATE 并且偶尔在日志中“跳过”我的转换过程。)

Sol*_*zky 9

问题是数据类型粒度/准确性和值来源的组合。

首先,DATETIME每 3 毫秒才准确/粒度。因此,从更精确的数据类型(例如DATETIMEOFFSETor )转换DATETIME2不会只是向上或向下舍入到最接近的毫秒,它可能会相差 2 毫秒。

其次,文档似乎暗示了值的来源不同。SYS* 函数使用高精度 FileTime 函数。

SYSDATETIMEOFFSET文档说明:

SQL Server 使用 GetSystemTimeAsFileTime() Windows API 获取日期和时间值。

GETUTCDATE文档指出:

此值源自运行 SQL Server 实例的计算机的操作系统。

然后,在About Time文档中,图表显示以下两种(多种)类型:

  • System Time = "年、月、日、小时、秒和毫秒,取自内部硬件时钟。"
  • 文件时间 = “自 1601 年 1 月 1 日以来 100 纳秒间隔的数量。”

其他线索在StopWatch该类的 .NET 文档中(我用粗斜体强调):

  • 秒表课

    秒表通过计算底层计时器机制中的计时器滴答来测量经过的时间。如果安装的硬件和操作系统支持高分辨率性能计数器,则 Stopwatch 类使用该计数器来测量经过的时间。否则,秒表类使用系统计时器来测量经过的时间。

  • 秒表.IsHighResolution 字段

    Stopwatch 类使用的计时器取决于系统硬件和操作系统。如果秒表计时器基于高分辨率性能计数器,则IsHighResolution 为true。否则, IsHighResolution 为false,表示秒表计时器基于系统计时器

因此,存在具有不同精度和不同来源的不同“类型”时间。

但是,即使这是一个非常松散的逻辑,测试这两种类型的函数作为DATETIME值的来源证明了这一点。来自问题的查询的以下改编显示了这种行为:

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;
Run Code Online (Sandbox Code Playgroud)

返回:

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
Run Code Online (Sandbox Code Playgroud)

从上面的结果可以看出,UTC 和 UTC2 都是DATETIME数据类型。@UTC2设置 viaSYSUTCDATETIME()并且设置在之后@Offset(也取自 SYS* 函数),但在@UTC设置之前通过GETUTCDATE(). 然而,@UTC2似乎来到了之前@UTC。其中的 OFFSET 部分与任何事情都完全无关。

然而,公平地说,这仍然不是严格意义上的证明。@MartinSmith 跟踪GETUTCDATE()电话并发现以下内容:

在此处输入图片说明

我在这个调用堆栈中看到了三件有趣的事情:

  1. Is 从一个调用开始,该调用GetSystemTime()返回一个精确到毫秒的值。
  2. 它使用 DateFromParts(即没有转换公式,只使用单个部分)。
  3. 底部有一个对QueryPerformanceCounter的调用。这是一种高分辨率差分计数器,可用作“校正”的一种手段。我已经看到一些建议使用一种类型随着时间的推移调整另一种类型,因为长间隔慢慢不同步(参见如何在 .NET/C# 中获取刻度精度的时间戳?关于 SO)。这在调用时不显示SYSDATETIMEOFFSET

这里有很多关于不同类型时间、不同来源、漂移等的好信息:获取高分辨率时间戳

实际上,比较不同精度的值是不合适的。例如,如果您有2017-06-07 12:01:58.8770011and 2017-06-07 12:01:58.877,那么您如何知道精度较低的值大于、小于或等于精度较高的值?比较它们会假设不那么精确的实际上是2017-06-07 12:01:58.8770000,但谁知道这是否正确?实际时间可能是2017-06-07 12:01:58.87700052017-06-07 12:01:58.8770111

但是,如果您有DATETIME数据类型,那么您应该使用 SYS* 函数作为源,因为它们更准确,即使由于值被强制转换为不太精确的类型而损失了一些精度。沿着这些路线,使用SYSUTCDATETIME()而不是SYSDATETIMEOFFSET()仅通过调用来调整它似乎更有意义SWITCHOFFSET(@Offset, 0)