Почему C# DateTime.Now/DateTime.UtcNow опережает SYSUTCDATETIME()/SYSDATETIME() SQL Server, хотя код C# выполняется до SQL-запроса

Я хочу знать причину, по которой моя дата С# больше даты SQL, хотя сначала выполняется код С#, а после этого SQL-запрос,

Логически дата SQL должна быть больше даты C#.

Для справки: приложение .NET и SQL Server находятся на моем локальном компьютере.

Код С#:


using System.Data;
using System.Data.SqlClient;

for (int i = 1; i <= 20; i++)
{
    AddRecord();
}
Console.WriteLine("20 records added in database....");

void AddRecord()
{
    try
    {
        string ConnectionString = @"data source=OM5\SQL2019; database=TestDb; integrated security=SSPI";
        using (SqlConnection connection = new SqlConnection(ConnectionString))
        {
            SqlCommand cmd = new SqlCommand()
            {
                CommandText = "SP_AddRecord",
                Connection = connection,
                CommandType = CommandType.StoredProcedure
            };

            SqlParameter param1 = new SqlParameter
            {
                ParameterName = "@CSharp_DateNow",
                SqlDbType = SqlDbType.DateTime2,
                Value = DateTime.Now,
                Direction = ParameterDirection.Input
            };
            cmd.Parameters.Add(param1);

            SqlParameter param2 = new SqlParameter
            {
                ParameterName = "@CSharp_DateUTCNow",
                SqlDbType = SqlDbType.DateTime2,
                Value = DateTime.UtcNow,
                Direction = ParameterDirection.Input
            };
            cmd.Parameters.Add(param2);

            connection.Open();
            cmd.ExecuteNonQuery();
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Exception Occurred: {ex.Message}");
    }
}

SQL:

CREATE TABLE [dbo].[Records](
    [Id] [int] PRIMARY KEY IDENTITY(1,1) NOT NULL,
    [SQL_SysDateTime] [datetime2](7) NOT NULL,
    [CSharp_DateNow] [datetime2](7) NOT NULL,
    [SQL_SysUTCDateTime] [datetime2](7) NOT NULL,
    [CSharp_DateUTCNow] [datetime2](7) NOT NULL
)

CREATE OR ALTER   PROCEDURE [dbo].[SP_AddRecord]
@CSharp_DateNow datetime2,
@CSharp_DateUTCNow datetime2
AS
BEGIN
    SET NOCOUNT ON;

     insert into Records(SQL_SysDateTime, CSharp_DateNow, SQL_SysUTCDateTime, CSharp_DateUTCNow) values
     (SYSDATETIME(),@CSharp_DateNow,SYSUTCDATETIME(),@CSharp_DateUTCNow)
END

Результат в таблице

SQL_SysDateTime CSharp_DateNow Диф. (РС) SQL_SysUTCDateTime CSharp_DateUTCNow Диф. (РС) 2024-07-26 13:26:35.2898391 2024-07-26 13:26:34.9701658 319 2024-07-26 07:56:35.2898391 2024-07-26 07:56:34.9726788 317 2024-07-26 13:26:35.3054610 2024-07-26 13:26:35.3174393 -12 2024-07-26 07:56:35.3054610 2024-07-26 07:56:35.3174492 -12 2024-07-26 13:26:35.3210815 2024-07-26 13:26:35.3217354 0 2024-07-26 07:56:35.3210815 2024-07-26 07:56:35.3217461 0 2024-07-26 13:26:35.3210815 2024-07-26 13:26:35.3261818 -5 2024-07-26 07:56:35.3210815 2024-07-26 07:56:35.3261915 -5 2024-07-26 13:26:35.3367030 2024-07-26 13:26:35.3310309 5 2024-07-26 07:56:35.3367030 2024-07-26 07:56:35.3310384 5 2024-07-26 13:26:35.3367030 2024-07-26 13:26:35.3411312 -5 2024-07-26 07:56:35.3367030 2024-07-26 07:56:35.3411394 -5 2024-07-26 13:26:35.3367030 2024-07-26 13:26:35.3418632 -5 2024-07-26 07:56:35.3367030 2024-07-26 07:56:35.3418676 -5 2024-07-26 13:26:35.3367030 2024-07-26 13:26:35.3430069 -7 2024-07-26 07:56:35.3367030 2024-07-26 07:56:35.3430104 -7 2024-07-26 13:26:35.3367030 2024-07-26 13:26:35.3437519 -7 2024-07-26 07:56:35.3367030 2024-07-26 07:56:35.3437554 -7 2024-07-26 13:26:35.3367030 2024-07-26 13:26:35.3446140 -8 2024-07-26 07:56:35.3367030 2024-07-26 07:56:35.3446172 -8 2024-07-26 13:26:35.3367030 2024-07-26 13:26:35.3452865 -9 2024-07-26 07:56:35.3367030 2024-07-26 07:56:35.3452894 -9 2024-07-26 13:26:35.3367030 2024-07-26 13:26:35.3459309 -9 2024-07-26 07:56:35.3367030 2024-07-26 07:56:35.3459336 -9 2024-07-26 13:26:35.3367030 2024-07-26 13:26:35.3466520 -10 2024-07-26 07:56:35.3367030 2024-07-26 07:56:35.3466552 -10 2024-07-26 13:26:35.3367030 2024-07-26 13:26:35.3475280 -11 2024-07-26 07:56:35.3367030 2024-07-26 07:56:35.3475305 -11 2024-07-26 13:26:35.3367030 2024-07-26 13:26:35.3486445 -12 2024-07-26 07:56:35.3367030 2024-07-26 07:56:35.3486474 -12 2024-07-26 13:26:35.3367030 2024-07-26 13:26:35.3492964 -13 2024-07-26 07:56:35.3367030 2024-07-26 07:56:35.3492991 -13 2024-07-26 13:26:35.3367030 2024-07-26 13:26:35.3501936 -14 2024-07-26 07:56:35.3367030 2024-07-26 07:56:35.3501961 -14 2024-07-26 13:26:35.3367030 2024-07-26 13:26:35.3506370 -14 2024-07-26 07:56:35.3367030 2024-07-26 07:56:35.3506392 -14 2024-07-26 13:26:35.3367030 2024-07-26 13:26:35.3511339 -15 2024-07-26 07:56:35.3367030 2024-07-26 07:56:35.3511362 -15 2024-07-26 13:26:35.3367030 2024-07-26 13:26:35.3517053 -15 2024-07-26 07:56:35.3367030 2024-07-26 07:56:35.3517087 -15

Мне нужна реальная причина или подлинный источник, который может это объяснить.

Предположительно, время на хосте, на котором вы запускаете приложение, и на хосте, на котором размещен SQL Server, различаются, поэтому вы получаете разные значения.

Thom A 25.07.2024 13:19

@ThomA - ОП сказал, что это один и тот же компьютер.

Enigmativity 25.07.2024 13:21

Я видел это, @Enigmativity, но они также используют строку подключения OM5\SQL2019, а это означает, что это разные устройства (так как они должны использовать .\SQL2019, если это локальное устройство). Меня также «обманули» достаточно раз, и я всегда задавался вопросом, когда пользователь говорит, что использует одно и то же устройство, хотя на самом деле это не так.

Thom A 25.07.2024 13:24

даже если вы вызываете их одновременно, у ЦП обычно есть планировщик, который создает внутреннюю очередь. иногда SQL первым получает поток, иногда C# получает его первым, иногда оба получают отдельные потоки одновременно.

Shamvil Kazmi 25.07.2024 13:25

Также отметим, что префикс sp_ зарезервирован Microsoft для специальных/системных процедур. Его не следует использовать для пользовательских процедур. Это связано с затратами на производительность и риском того, что ваша процедура просто не заработает через день после обновления/модернизации. Либо используйте другой префикс, либо (возможно, лучше) вообще не используйте префикс. Префикс sp_ по-прежнему запрещен?

Thom A 25.07.2024 13:26

@ShamvilKazmi Я также проверял это несколько раз и на другой машине, но результат тот же

Shivam Pal 25.07.2024 13:27

@ThomA - Достаточно справедливо. Вы имеете смысл.

Enigmativity 25.07.2024 13:28

@ShamvilKazmi код C# должен выполняться перед SQL, поскольку он передает значение SQL.

Dale K 25.07.2024 13:29

Там, где вы заметили разницу в -4, на самом деле разница равна -3.

HoneyBadger 25.07.2024 13:29

@ThomA Я хочу добавить еще одну вещь, которую я делаю, создавая Node Js API и используя ту же процедуру, но она дает правильное время. Их время JavaScript меньше, чем время Sql. Это тоже правильно.

Shivam Pal 25.07.2024 13:34

Я предполагаю, что при переводе даты и времени C# в дату и время sql происходит какое-то округление, что делает его неправильным. Можете ли вы создать таблицу с datetime2 и опубликовать результаты? SYSUTCDATETIME можно использовать по умолчанию.

siggemannen 25.07.2024 13:44

@Шивам, ты говоришь, что использовал datetime2, но никогда не публиковал такой код. Все несоответствия, кроме 3, можно объяснить точностью datetime, и ничто из указанного в вопросе не указывает на иное. Нет CREATE TABLE с использованием datetime2(7), нет хранимой процедуры с параметром datetime2(7) и SYSUTCDATETIME по умолчанию, нет кода C# с использованием SqlDbType.DateTime2. Остальные 3 случая могут быть задержками при запуске, подключении или сети. Вы уже получили ответ на вопрос.

Panagiotis Kanavos 25.07.2024 14:03

мне удалось воспроизвести эту проблему в своей системе. разница в МИКРОСЕКУНДАХ 98650 526 -835 -261 -666 -138 -553 -124 -618 -110 -508 -910 -191 -608 -98 -540 -94 -528 -940 -144

Shamvil Kazmi 25.07.2024 15:29

@Шивам - Он снова открыт.

Enigmativity 26.07.2024 01:06

@DaleK Да, я

Shivam Pal 26.07.2024 14:56
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
6
15
276
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вам нужно использовать SqlDbType.DateTime2, поскольку это обеспечивает более высокую точность.

Из документации

DateTime — Данные о дате и времени в диапазоне от 1 января 1753 года до 31 декабря 9999 года с точностью до 3,33 миллисекунды. (большинство ваших результатов расхождения находятся в диапазоне 3-4)

DateTime2 - Данные о дате и времени. Диапазон значений даты — с 1,1 января нашей эры по 31 декабря 9999 года нашей эры. Диапазон значений времени — от 00:00:00 до 23:59:59,9999999 с точностью до 100 наносекунд.

И sysdatetime потому что getdate возвращает datetime. И datetime2 в проке тоже.

Dale K 25.07.2024 13:34

Я уже пробовал это сделать, но результаты те же.

Shivam Pal 25.07.2024 13:37

@Шивам - покажи нам аналогичную таблицу с datetime2

Shamvil Kazmi 25.07.2024 13:38
Ответ принят как подходящий

Эти значения являются точными, но не точными.

Если вы возьмете разные значения из SQL_SysDateTime и сравните их...

SELECT MS_Diff = DATEDIFF(NANOSECOND, '2024-07-26 13:26:35.2898391', '2024-07-26 13:26:35.3054610')/1E6, 
       MS_Diff = DATEDIFF(NANOSECOND, '2024-07-26 13:26:35.3054610', '2024-07-26 13:26:35.3210815')/1E6, 
       MS_Diff = DATEDIFF(NANOSECOND, '2024-07-26 13:26:35.3210815', '2024-07-26 13:26:35.3367030')/1E6

Это возвращает 15.6219, 15.6205, 15.6215 как разницу между ними (в мс).

Как описано здесь SQL Server использует GetSystemTimeAsFileTime() для SYSDATETIME()/SYSUTCDATETIME().

Рэймонд Чен указывает здесь, что по умолчанию GetSystemTimeAsFileTime() не особенно точен, хотя упоминает периоды обновления по умолчанию для возвращаемого им значения 55 мс или 10 мс, а не 15,62, так что, по-видимому, с тех пор это изменилось.

На различных сайтах указано, что разрешение таймера по умолчанию в Windows 10 составляет 15,6 мс ( или, точнее, 15625000 нс), поэтому приведенные выше пробелы соответствуют этому значению.

Для C# документация DateTime.UtcNow не выглядит более многообещающе

Разрешение этого свойства зависит от системного таймера, который зависит от базовой операционной системы. Обычно оно составляет от 0,5 и 15 миллисекунд.

Так что остается вопрос, как добиться большей точности.

Вы отметили ядро ​​.NET. Для этого запроса на включение он теперь вызывает GetSystemTimePrecisionAsFileTime, когда он доступен (один из последних, упомянутых в сообщении Рэймонда Чена выше).

На моей локальной машине (Win 11) при выполнении следующего теста я обычно вижу разницу около 1 мс. (Но запуск powercfg -energy говорит мне о том, что различные запущенные у меня процессы (включая chrome.exe и MongoDB) запросили низкий интервал времени для разрешения таймера платформы)

SET NOCOUNT ON;

DECLARE @Times TABLE(insert_time datetime2)

DECLARE @Counter INT = 0

WHILE @Counter < 10000
BEGIN
INSERT @Times VALUES (SYSUTCDATETIME())
SET @Counter+=1;
END


SELECT  [rowcount] = COUNT(*), 
        insert_time, 
        prev_insert_time = LAG(insert_time) OVER (ORDER BY insert_time),
        diff_ms = DATEDIFF(NANOSECOND,LAG(insert_time) OVER (ORDER BY insert_time), insert_time)/1e6
FROM @Times
GROUP BY insert_time

SQL Server в настоящее время не имеет встроенного способа вызова GetSystemTimePreciseAsFileTime и возврата datetime2(7), поэтому, если это важно для вас, вам придется сделать это за пределами базы данных (вы также можете использовать для этого интеграцию CLR, но тогда сборку необходимо будет помечен как небезопасный для вызова функции WinAPI).

Выполнив приведенное выше в базе данных SQL Azure, я получил следующие результаты, поэтому не похоже, что она обновляется там чаще (и вы получаете только ~ 64 уникальных значения в секунду).

Интересно, заменив SYSUTCDATETIME() на GETUTCDATE(), я получаю разницу в 3.3333/3.3334 мс, так что это выглядит менее точным, но более точным. Предположительно это результат упомянутой здесь «коррекции».

Мне эта ситуация кажется далеко не идеальной. Есть запрос обратной связи Попросите SYSDATETIME() вернуть значение из GetSystemTimePrecisionAsFileTime(), но он имеет только 3 голоса и помечен как «В архиве», поэтому не уверен, означает ли это, что он никогда не будет рассмотрен.

количество строк время вставки prev_insert_time diff_ms 563 2024-07-29 07:21:30.6607494 НУЛЕВОЙ НУЛЕВОЙ 646 2024-07-29 07:21:30.6763740 2024-07-29 07:21:30.6607494 15,6246 659 2024-07-29 07:21:30.6919988 2024-07-29 07:21:30.6763740 15,6248 673 2024-07-29 07:21:30.7076257 2024-07-29 07:21:30.6919988 15,6269 666 2024-07-29 07:21:30.7232517 2024-07-29 07:21:30.7076257 15,626 659 2024-07-29 07:21:30.7390662 2024-07-29 07:21:30.7232517 15.8145 667 2024-07-29 07:21:30.7545026 2024-07-29 07:21:30.7390662 15.4364 660 2024-07-29 07:21:30.7701262 2024-07-29 07:21:30.7545026 15,6236 667 2024-07-29 07:21:30.7857507 2024-07-29 07:21:30.7701262 15,6245 668 2024-07-29 07:21:30.8013755 2024-07-29 07:21:30.7857507 15,6248 664 2024-07-29 07:21:30.8169996 2024-07-29 07:21:30.8013755 15,6241 631 2024-07-29 07:21:30.8326241 2024-07-29 07:21:30.8169996 15,6245 660 2024-07-29 07:21:30.8482510 2024-07-29 07:21:30.8326241 15,6269 662 2024-07-29 07:21:30.8638744 2024-07-29 07:21:30.8482510 15,6234 670 2024-07-29 07:21:30.8795009 2024-07-29 07:21:30.8638744 15,6265 185 2024-07-29 07:21:30.8951255 2024-07-29 07:21:30.8795009 15,6246

Но если C# и SQL считывают одни и те же системные часы, как SQL может показывать более низкое значение, чем C#? Я понимаю, что разрешение составляет 15 мс, но наверняка они должны считывать одно и то же значение? И как SQL может показывать одно и то же значение для нескольких вызовов одного и того же SP? Я думаю, это возможно, если все они находятся в пределах одного и того же интервала 15 мс?

Dale K 28.07.2024 06:15

Да, вполне ожидаемо, что SQL вернет те же значения, если он будет вызван до обновления времени. .NET, по-видимому, использует какой-то другой метод, лежащий в основе DateTime.UtcNow, кроме вызова GetSystemTimeAsFileTime. Возможно, один из последних, упомянутых в посте Рэймонда Чена.

Martin Smith 28.07.2024 06:20

Хорошо, это имеет смысл, хотите ли вы подвести итог этому в конце вашего вопроса, например. «Похоже, что SQL Server использует время обновления по умолчанию 15 мс, о чем свидетельствует тот факт, что его значение не меняется для нескольких вызовов. В то время как .NET, похоже, использует один из более точных методов и поэтому медленно опережает SQL до конца периода обновления».

Dale K 28.07.2024 06:23

хотя на самом деле .NET не документирован как более точный The resolution of this property depends on the system timer, which depends on the underlying operating system. It tends to be between 0.5 and 15 milliseconds.Learn.microsoft.com/en-us/dotnet/api/…

Martin Smith 28.07.2024 06:23

Это немного странно думать об этом: 15 мс — не очень высокое разрешение, если вы запускаете много обновлений в SQL.

Dale K 28.07.2024 06:23

Странно, что .NET утверждает, что он такой же... но поведение, похоже, говорит об обратном. По крайней мере, это хорошая теория :)

Dale K 28.07.2024 06:24

Похоже, .NET Core улучшил эту область github.com/dotnet/coreclr/pull/9736

Martin Smith 28.07.2024 06:29

Хорошая находка! Хотя в два раза медленнее... надо меньше называть :)

Dale K 28.07.2024 06:31

Другие вопросы по теме