SQL Server 中 timestamp/rowversion 并发控制本质是依赖数据库单调递增计数器的条件更新机制,必须转为 bigint 才能安全比对,因其为 binary(8) 类型,直接字符串比较易因格式不规范导致隐式转换失败或静默不匹配。

SQL Server 中用 timestamp(现推荐用 rowversion)做并发控制,本质是“带条件更新 + 自动版本号校验”,不是靠时间精度,而是靠数据库内部的单调递增计数器。只要用对了,一次 SQL 就能判断是否被他人抢先改过,不需要额外查、锁或事务隔离级别调高。
为什么必须把 timestamp 转成 bigint 才能安全比对
SQL Server 的 timestamp 是 8 字节二进制值(binary(8)),不可直接参与数值或字符串比较。直接写 WHERE rowVersion = '0x00000000000004B1' 看似可行,但容易因隐式转换失败或截断出错;更危险的是前端传回的字符串若含空格、大小写不一致、前导零缺失,就会导致永远不匹配。
- 读取时统一用
CONVERT(bigint, rowVersion)转为长整型,比如1201,再存到隐藏字段或 API 响应里 - 更新时 WHERE 条件用
rowVersion = @originalVersion,且参数类型必须是SqlDbType.Binary或显式转为binary(8);但更稳妥的做法是:前端传bigint值,后端用BitConverter.GetBytes(longValue)构造byte[8]再传参 - 千万别用字符串拼接 SQL——
"AND rowVersion = '" + hexStr + "'"这种写法在十六进制格式不规范时会静默失败
UPDATE ... WHERE id = @id AND rowVersion = @expected 执行后怎么判断是否冲突
这条语句本身不报错,它只是“没改到任何行”。关键看返回的受影响行数(ExecuteNonQuery() 返回值):
- 返回
1:更新成功,rowVersion自动更新为新值 - 返回
0:要么记录不存在,要么rowVersion已被别人改过 → 并发冲突 - 绝不能只靠异常捕获来判断冲突,因为这不是数据库错误,而是业务逻辑分支
示例 C# 片段:
var cmd = new SqlCommand("UPDATE products SET buyer = @buyer WHERE productID = @id AND rowVersion = @ver", conn);
cmd.Parameters.AddWithValue("@buyer", userId);
cmd.Parameters.AddWithValue("@id", productId);
cmd.Parameters.Add("@ver", SqlDbType.Binary).Value = originalRowVersionBytes; // 注意类型
int rows = (int)cmd.ExecuteNonQuery();
if (rows == 0) {
throw new InvalidOperationException("数据已被其他用户修改,请刷新后重试");
}使用 rowversion 字段时最容易踩的三个坑
SQL Server 2005 起已将 timestamp 标记为“向后兼容”,官方文档明确建议用 rowversion 作为列类型名(二者完全等价,只是语义更准确):
- 一张表只能有一个
rowversion列,建第二个会报错Msg 2739 -
rowversion列不允许手动插入或更新(哪怕用SET IDENTITY_INSERT ON也不行),否则报错Msg 272 - 如果用 Entity Framework 或 Dapper 等 ORM,需显式配置该列为
ConcurrencyCheck或IsRowVersion = true,否则生成的 UPDATE 语句不会带上该条件
真正难处理的不是“怎么写 SQL”,而是当冲突发生时,要不要合并变更、要不要展示差异、要不要自动重载最新数据——这些都得由业务层决定,rowversion 只负责干净利落地告诉你:“这行已经不是你打开时的样子了”。

















