Почему GETUTCDATE раньше, чем SYSDATETIMEOFFSET?

8

В качестве альтернативы, как Microsoft сделала возможным путешествие во времени?

Рассмотрим этот код:

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устанавливается раньше @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

Таким образом, точность даты и времени допускает 0,880 в качестве допустимого значения.

Даже примеры GETUTCDATE от Microsoft показывают, что значения SYS * позже, чем у более старых методов, несмотря на то, что они были ВЫБРАНЫ ранее :

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

Я предполагаю, что это происходит потому, что они исходят из различной базовой системной информации. Кто-нибудь может подтвердить и предоставить подробности?

Документация Microsoft SYSDATETIMEOFFSET гласит: «SQL Server получает значения даты и времени с помощью Windows API GetSystemTimeAsFileTime ()» (спасибо srutzky), но их документация GETUTCDATE гораздо менее конкретна, говоря только о том, что «значение получено из операционной системы компьютер, на котором работает экземпляр SQL Server ".

(Это не совсем академично. Я столкнулся с незначительной проблемой, вызванной этим. Я обновлял некоторые процедуры, чтобы использовать SYSDATETIMEOFFSET вместо GETUTCDATE, в надежде на большую точность в будущем, но я начал получать странный порядок, потому что другие процедуры были все еще использую GETUTCDATE и иногда "забегаю вперед" из моих преобразованных процедур в журналах.)

Райли Майор
источник
1
Я не знаю, что есть какое-либо объяснение, кроме округления или округления. Когда вам нужно округлить любое значение до одного с меньшей точностью, и когда в некоторых случаях вам нужно округлить, а в других - округлить (простые математические правила в игре здесь), вы можете ожидать, что в некоторых случаях округленное значение будет либо ниже, либо выше исходного, более точное значение. Если вы хотите убедиться, что менее точное значение всегда перед (или всегда после) более точным, вы всегда можете манипулировать им на 3 миллисекунды с помощью DATEADD ().
Аарон Бертран
(Кроме того, почему бы просто не изменить ВСЕ процедуры одновременно, чтобы использовать более новое, более точное значение?)
Аарон Бертран
Я не думаю, что это может быть такой же простой ответ, как округление, потому что если вы конвертируете значение datetimeoffset 2017-06-07 12: 01: 58.8801139 -05: 00 непосредственно в datetime, оно выберет 2017-06-07 12:01 : 58.880, не 2017-06-07 12: 01: 58.877. Так что либо GETUTCDATE внутренне округляется по-разному, либо это разные источники.
Райли Майор
Я бы предпочел делать их постепенно, чтобы изменить как можно меньше вещей одновременно. scribnasium.com/2017/05/deploying-the-old-fashioned-way Я, в конце концов, сделаю их партиями или буду жить с несогласованностью в журналах.
Райли Майор
2
Кроме того, я хотел бы добавить, что путешествие во времени всегда возможно в компьютерах . Вы никогда не должны кодировать, ожидая, что время всегда идет вперед. Причиной этого является смещение и коррекция системного времени, в основном за счет службы времени Windows , а также из-за настроек пользователя. На самом деле очень часто встречаются дрейф и коррекция за миллисекунды.
Ремус Русану

Ответы:

9

Проблема заключается в сочетании гранулярности / точности типов данных и источника значений.

Во-первых, DATETIMEэто только точное / гранулированное значение каждые 3 миллисекунды. Следовательно, преобразование из более точного типа данных , такие как DATETIMEOFFSETили DATETIME2не будет просто круглым вверх или вниз до ближайшей миллисекунды, это может быть 2 миллисекунды разные.

Во-вторых, документация подразумевает разницу в том, откуда берутся значения. Функции SYS * используют высокоточные функции FileTime.

Документация SYSDATETIMEOFFSET гласит:

SQL Server получает значения даты и времени с помощью Windows API GetSystemTimeAsFileTime ().

в то время как документация GETUTCDATE гласит:

Это значение является производным от операционной системы компьютера, на котором работает экземпляр SQL Server.

Затем в документации « О времени» диаграмма показывает следующие два (из нескольких) типов:

  • Системное время = «Год, месяц, день, час, секунда и миллисекунда, взятые из внутренних аппаратных часов».
  • File Time = «Количество интервалов в 100 наносекунд с 1 января 1601 года».

Дополнительные подсказки находятся в документации .NET для StopWatchкласса (выделение выделено жирным курсивом):

  • Класс секундомера

    Секундомер измеряет прошедшее время, подсчитывая отметки времени в базовом механизме таймера. Если установленное оборудование и операционная система поддерживают счетчик производительности с высоким разрешением, то класс секундомера использует этот счетчик для измерения прошедшего времени. В противном случае класс Секундомер использует системный таймер для измерения прошедшего времени.

  • Секундомер.IsHighResolution Field

    Таймер, используемый классом секундомера, зависит от системного оборудования и операционной системы. 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;

Возвращает:

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

Как видно из приведенных выше результатов, UTC и UTC2 являются DATETIMEтипами данных. @UTC2устанавливается через SYSUTCDATETIME()и устанавливается после @Offset(также берется из функции SYS *), но до @UTCкоторого устанавливается через GETUTCDATE(). Тем не менее, @UTC2кажется, пришел раньше @UTC. ОФСЕТНАЯ часть этого абсолютно не связана ни с чем.

ОДНАКО, чтобы быть справедливым, это все еще не доказательство в строгом смысле. @MartinSmith отследил GETUTCDATE()звонок и обнаружил следующее:

введите описание изображения здесь

Я вижу три интересные вещи в этом стеке вызовов:

  1. Is начинается с вызова, GetSystemTime()который возвращает значение с точностью до миллисекунд.
  2. Он использует DateFromParts (то есть нет формулы для преобразования, просто используйте отдельные части).
  3. Есть вызов QueryPerformanceCounter внизу. Это счетчик разности высокого разрешения, который можно использовать как средство «коррекции». Я видел некоторые предложения использовать один тип для настройки другого с течением времени, так как длинные интервалы медленно теряют синхронизацию (см. Как получить временную метку точности тиков в .NET / C #? На SO). Это не отображается при звонке SYSDATETIMEOFFSET.

Здесь много полезной информации о различных типах времени, различных источниках, дрейфе и т. Д.: Получение меток времени с высоким разрешением .

Действительно, неуместно сравнивать значения разной точности. Например, если у вас есть 2017-06-07 12:01:58.8770011и 2017-06-07 12:01:58.877, то как узнать, что значение с меньшей точностью больше, меньше или равно значению с большей точностью? Сравнение их предполагает, что на самом деле менее точный 2017-06-07 12:01:58.8770000, но кто знает, правда это или нет? Реальное время могло быть или 2017-06-07 12:01:58.8770005или 2017-06-07 12:01:58.8770111.

Однако, если у вас есть DATETIMEтипы данных, вам следует использовать функции SYS * в качестве источника, поскольку они более точны, даже если вы теряете некоторую точность, поскольку значение принудительно переводится в менее точный тип. И в этом отношении, кажется, имеет больше смысла использовать, SYSUTCDATETIME()а не вызывать SYSDATETIMEOFFSET()только для настройки через SWITCHOFFSET(@Offset, 0).

Соломон Руцкий
источник
В моей системе, GETUTCDATEкажется, звонит GetSystemTimeи SYSDATETIMEOFFSETзвонит, GetSystemTimeAsFileTimeхотя оба, по-видимому, сводятся к одному и тому же: blogs.msdn.microsoft.com/oldnewthing/20131101-00/?p=2763
Мартин Смит,
1
@MartinSmith (1/2) Я потратил некоторое время на это объявление, сегодня у меня больше нет времени для этого. У меня нет простого способа протестировать C ++ Win API, который вызывается и упоминается в том блоге, который вы упомянули. Я могу конвертировать туда и обратно между FileTime и DateTime в .NET, но DateTime не SystemTime, поскольку он может обрабатывать полную точность, но он был без потерь. Я не могу доказать / опровергнуть эти GetSystemTimeвызовы GetSystemTimeAsFileTime , но я обратил внимание на интересные вещи в стеке вызовов, который вы разместили в комментарии к вопросу: 1) он использует DateFromParts, так что, скорее всего, он усечен до мс (продолжение)
Соломон Руцки,
@MartinSmith (2/2) вместо округленного (поскольку SystemTime снижается только до миллисекунд), и 2) происходит вызов QueryPerformanceCounter по направлению вниз. Это счетчик разности высокого разрешения, который можно использовать как средство «коррекции». Я видел некоторые предположения об использовании одного типа для настройки другого с течением времени, так как длинные интервалы медленно теряют синхронизацию. Здесь много полезной информации: получение меток времени с высоким разрешением . Если они начнутся в одно и то же время, потеря составляет 1 из 2 шагов выше.
Соломон Руцкий
Я полагал, что Офсет был красной сельдью, но не создал более абстрактного теста. Я должен был, как только увидел документы Microsoft, показать несоответствие. Спасибо за подтверждение этого.
Райли Майор
Моя проблема в том, что у меня есть несколько процедур, делающих это. Все они используют GETUTCDATE сейчас. Если я заменю некоторые функции на SYS * (с прямым смещением или UTC), они начинают выходить из строя с другими, используя функции GET *. Но я могу или жить с этим или изменить их всех сразу. Не огромная сделка. Это просто зажгло моё любопытство.
Райли Майор