【问题标题】:Why does my SQL Server trigger write two records with the same time stamp from different transactions?为什么我的 SQL Server 触发器会从不同的事务中写入两条具有相同时间戳的记录?
【发布时间】:2020-03-26 00:46:46
【问题描述】:

我有一个带有整数 Status 列的 SQL Server 表。我的代码更新记录的Status,然后在几毫秒后再次将其更新为不同的值。

此表有一个触发器,每次Status 值更改时,它都会将一条记录插入到单独的History 表中。

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 Server 正在保存触发事件并立即触发它们。 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

附录: 我用第四个字段 (varchar) 更新了 History 表,并更新了 Trigger 以将 @@SPID 和 CURRENT_TRANSACTION_ID() 写入该字段,用冒号分隔。这是我得到的:

每次更新都是在同一个会话和一个单独的事务中执行的,这两者都是我所期望的,但它仍然不能解释为什么记录共享相同的时间戳。

【问题讨论】:

  • 嗯,documentation 声明精度取决于底层 Windows API 并且是有限的。也许事情发生得太快了,下一个时间戳与上一个时间戳不同。
  • 也许你也应该edit这个问题并解释由于重复的时间戳你面临的问题。也许有解决方法。
  • 您能否尝试将@@spid 存储到历史记录表中,看看这两个调用是否是两个单独的会话(而不是一个单独的会话)?
  • 我在我们的密钥管理系统中遇到了类似的跟踪历史记录问题。唯一可行的方法是将时间戳与整数 IDENTITY 列配对。该组合将判断哪条记录真正是第一条记录。
  • 请参阅此处的答案,了解使用 SEQUENCE 替换亚毫秒精度的最后几个(无意义的)数字的选项。 stackoverflow.com/questions/51459805/…

标签: c# sql-server database-trigger ormlite-servicestack


【解决方案1】:

感谢sticky bitlptr。看来操作系统是罪魁祸首。

根据SQL Server documentation

SQL Server 使用 GetSystemTimeAsFileTime() Windows API 获取日期和时间值。准确性取决于运行 SQL Server 实例的计算机硬件和 Windows 版本。此 API 的精度固定为 100 纳秒。精度可以通过使用 GetSystemTimeAdjustment() Windows API 来确定。

This long and complex article 但是,在解释原因方面做得更好。我不能说我理解所有这些,但底线似乎是 SYSUTCDATETIME() 使用一个操作系统工具来读取时钟值,该时钟值更新频率不足以获得我们期望的精度。上面的文章指的是一台典型的机器每秒更新 64 次时钟,这甚至不是毫秒精度。这篇文章确实指出有一些方法可以获得更准确的结果,尤其是使用较新版本的 Windows,但我无法从 SQL 查询轻松访问这些方法。

只要我们了解它发生的原因,我们就可以接受结果。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-19
    • 2019-12-08
    • 1970-01-01
    • 2018-07-09
    • 2019-07-15
    相关资源
    最近更新 更多