Почему мой SQL серверный триггер записывает две записи с одинаковой отметкой времени из разных транзакций? - PullRequest
1 голос
/ 26 марта 2020

У меня есть SQL Таблица сервера с целым столбцом Status. Мой код обновляет Status записи, а затем, через миллисекунды, снова обновляет ее до другого значения.

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

В таблице History есть столбцы для:

  1. Id uniqueidentifier: значение PK обновленной записи (примечание: НЕ внешний ключ - нам не нужна ссылочная целостность )
  2. Status int: новое значение состояния обновленной записи
  3. TimeUtc DateTime2(7): время обновления

Файл журнала NLog показывает, что мы делаем два отдельных вызова базы данных UPDATE в закрытое, но разное время. Но когда мы смотрим в базу данных впоследствии, значение TimeUtc для двух записей истории идентично микросекунде:

Id: CD83...  Status: 4  TimeUtc: 2020-3-20 16:14:26.6952631
Id: CD83...  Status: 5  TimeUtc: 2020-3-20 16:14:26.6952631

Почему это происходит? Чтобы сделать это еще более странным, иногда, когда мы запускаем параллельный код, две отдельные записи будут обновляться практически в одно и то же время, и все четыре записи истории будут иметь одну и ту же метку времени.

Id: CD83...  Status: 4  TimeUtc: 2020-3-20 16:14:26.6952631
Id: CD83...  Status: 5  TimeUtc: 2020-3-20 16:14:26.6952631
Id: 06EA...  Status: 4  TimeUtc: 2020-3-20 16:14:26.6952631
Id: 06EA...  Status: 5  TimeUtc: 2020-3-20 16:14:26.6952631

Мы используем ServiceStack OrmLite для обновления значений и документы говорят мне, что каждый вызов - это отдельная транзакция, которая (я думал) выдаст два отдельных события триггера, но, возможно, это неправильно. Похоже, SQL Сервер копирует события триггера и запускает их все сразу. Может ли ServiceStack OrmLite объединять вызовы внутри единой транзакции?

Вот определение триггера. Дайте мне знать, если понадобится больше деталей:

CREATE TRIGGER [dbo].[ChangeStatus] 
ON [dbo].[TableWithStatus]
AFTER INSERT, UPDATE    
AS 
BEGIN
    SET NOCOUNT ON;

    -- On insert, or on update where status changed
    INSERT INTO History
        SELECT i.[Id], i.[Status], SYSUTCDATETIME()
        FROM Inserted i 
        LEFT JOIN Deleted d ON i.Id = d.Id
        WHERE i.[Status] IS NOT NULL 
          AND (d.[Status] IS NULL OR i.[Status] <> d.[Status])
END
GO

ADDENDUM: я обновил таблицу History четвертым полем (varchar) и обновил триггер, чтобы записать @@ SPID и CURRENT_TRANSACTION_ID () в это поле, разделив их двоеточие Вот что я получил:

History table

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

1 Ответ

1 голос
/ 26 марта 2020

Спасибо Sticky Bit и lptr . Похоже, что операционная система является виновником.

В соответствии с SQL Документация сервера :

SQL Сервер получает значения даты и времени с помощью API GetSystemTimeAsFileTime () Windows. Точность зависит от аппаратного обеспечения компьютера и версии Windows, на которой работает экземпляр SQL Server. Точность этого API установлена ​​на уровне 100 наносекунд. Точность можно определить с помощью API GetSystemTimeAdjustment () Windows.

Эта длинная и сложная статья , однако, лучше объясняет почему. Я не могу сказать, что понимаю все это, но суть в том, что SYSUTCDATETIME () использует инструмент ОС, который считывает значение тактовой частоты, которое обновляется недостаточно часто, чтобы получить ожидаемую точность. В статье выше говорится о типичной машине, обновляющей свои часы 64 раза в секунду, что даже с точностью до миллисекунды. В статье указано, что есть способы получить более точные результаты, особенно с новыми версиями Windows, но я не могу легко получить доступ к этим методам из запроса SQL.

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

...