sql server 的 timestamp 实际是 rowversion,为8字节自动递增二进制序列号,与时间无关;需用 datetime2 等存储真实时间;乐观并发控制必须在 update 中显式比对 rowversion 值,否则失效。

SQL Server 中 timestamp 列不是时间类型,而是 rowversion
很多人在建表时写 timestamp,以为它存的是当前时间,结果发现值像 0x00000000000004B1 这样无法直接读取。其实 SQL Server 的 timestamp 是自动递增的二进制序列号(8 字节),语义上等价于 rowversion —— 它只反映行修改顺序,和系统时间无关。如果你需要真实时间,请用 datetime2 或 datetimeoffset 配合手动更新逻辑。
UPDATE 语句中必须显式引用 rowversion 列做条件
冲突检测靠的是“读取时的版本值”和“提交时数据库当前版本值”是否一致。不加条件就 UPDATE,等于放弃检测。
- ✅ 正确写法:
UPDATE Orders SET Status = @newStatus WHERE OrderID = @id AND VersionStamp = @originalVersion - ❌ 错误写法:
UPDATE Orders SET Status = @newStatus WHERE OrderID = @id(完全绕过并发检查) - 如果
@@ROWCOUNT = 0,说明没更新成功 → 很可能发生了冲突,需查新值或抛错
存储过程里要返回实际影响行数或新版本值
光靠 @@ROWCOUNT 不够:用户需要知道是“没找到记录”还是“版本不匹配”。更稳妥的做法是在 UPDATE 后立刻 SELECT 新值:
UPDATE Orders SET Status = @newStatus, UpdatedAt = GETUTCDATE() WHERE OrderID = @id AND VersionStamp = @originalVersion; <p>IF @@ROWCOUNT = 0 BEGIN -- 冲突或记录不存在,返回当前最新状态供前端决策 SELECT OrderID, Status, VersionStamp, UpdatedAt FROM Orders WHERE OrderID = @id; END</p>
这样调用方能区分:是数据已被别人改了(有返回值),还是根本没这条记录(空结果)。
注意 rowversion 在 INSERT/UPDATE 触发器或复制场景中的行为
rowversion 值由 SQL Server 自动维护,但有几个边界情况容易出问题:
- 触发器中执行 UPDATE 可能导致
rowversion多次变更,而应用层拿到的仍是初始读取值 → 检测失效 - 对等复制(Peer-to-Peer Replication)会额外添加隐藏列如
$sys_p2p_cd_id,此时仅靠rowversion不足以识别跨节点冲突 - 如果表启用了
temporal table,历史版本不参与rowversion比较,别误以为能靠它查“谁在什么时候改的”
真正难处理的从来不是单机事务里的版本比对,而是当业务逻辑分散在多个存储过程、触发器、甚至外部服务中时,rowversion 的“可见性窗口”会被拉长,导致冲突感知滞后。这时候与其硬扛,不如把关键字段的乐观锁逻辑收口到一个原子化更新过程中。











