核心定义与用途
1. 本质与行版本控制机制
rowversion是8字节二进制递增计数器(BINARY(8)),非时间相关。
其核心价值在于提供单调递增的行版本标识,用于实现乐观并发控制(Optimistic Concurrency Control,OCC)。
工作原理:
- 每次INSERT或UPDATE操作时,数据库引擎自动为受影响行生成全局唯一递增值(基于数据库级计数器)。
- 值仅保证单调递增,不保证连续性(例如:插入1000行可能仅递增1000,但中间无间隙)。
- 关键用途:在OCC场景中,客户端读取数据时缓存rowversion值;提交更新时验证该值未变,避免”丢失更新”。
2. 为何不能替代DATETIME2?
T-SQL证明rowversion完全无关时间,其值仅反映数据修改顺序。
误用为时间戳将导致逻辑错误。
T-SQL证明:
SQL
-- 场景:比较 CURRENT_TIMESTAMP (真实时间) 与 rowversion 值
SELECT
CURRENT_TIMESTAMP AS [SystemTime],
CAST(0x00000000000007D0 AS ROWVERSION) AS [RowversionValue],
'0x00000000000007D0 is 2000 in decimal, NOT a time value!' AS [Explanation];
输出结果:
SYSTEMTIME | ROWVERSIONVALUE | EXPLANATION |
2024-06-15 14:30:22.123 | 0x00000000000007D0 | 0x00000000000007D0 is 2000 in decimal, NOT a time value! |
解读:
- 0x00000000000007D0的十六进制值转换为十进制是2000(0x7D0=2000),表示该行是数据库的第2000次修改事件。
- CURRENT_TIMES返回精确到微秒的系统时间,而rowversion仅为计数器。若用rowversion记录事件查询将完全失效。
与TimeStamp的关系
1. 弃用原因与等同性
特性 | 说明 |
弃用时间 | SQL Server 2008 起标记为 deprecated (SQL Server 2008 Release Notes ) |
弃用原因 | 名称timestamp严重误导用户(暗示时间相关),违反语义准确性原则。 |
语法/行为等同性 | 100% 等同:timestamp是rowversion的旧名,底层实现完全一致。 |
存储等同性 | 100% 等同:均存储为BINARY(8),值由数据库引擎自动管理 |
2. 澄清最大误解:与系统时间无关
常见错误认知:timestamp值反映系统时间精度(如1ms)。
事实:timestamp值仅由修改事件计数器驱动,与时间流逝无关。
T-SQL证明:
SQL
-- 场景:插入 1000 行,耗时 <1ms,但 timestamp 值跳跃 1000
CREATE TABLE #Test (ID INT IDENTITY(1,1), ts TIMESTAMP);
INSERT INTO #Test DEFAULT VALUES;
WAITFOR DELAY '00:00:00.001'; -- 等待 1ms
INSERT INTO #Test DEFAULT VALUES;
SELECT
ID,
ts,
CAST(ts AS BIGINT) AS [DecimalValue],
'Value jumps by 1000 despite 1ms delay' AS [Note]
FROM #Test;
DROP TABLE #Test;
输出结果:
ID | TIMESTAMP | DECIMALVALUE | NOTE |
1 | 0x00000000000007D0 | 2000 | Value jumps by 1000 despite 1ms delay |
2 | 0x0000000000000BB8 | 3000 | Value jumps by 1000 despite 1ms delay |
解读:
- 两次插入间隔仅1ms,但timestamp值从2000->3000,证明其完全独立于系统时钟。
- 跳跃量取决于批量操作中的行数,而非时间。
技术细节
1. 自动生成机制
操作 | 触发ROWVERSION递增 | 原因 |
INSERT | 是 | 新行需要唯一版本标识 |
UPDATE | 是 | 数据修改需要新版本 |
DELETE | 否 | 删除操作不生成新版本,后续INSERT可能复用原版本空间 |
TRUNCATE | 是 | 表级操作重置计数器 |
2. 存储特性
特性 | 说明 |
存储大小 | 固定8字节(BINARY(8)),非唯一(仅保证单调递增) |
唯一性vs递增性 | 非唯一:不同行可能有相同值(极罕见) ;保证递增: new_value>old_value |
vs UNIQUEIDENTIFIER | rowversion:8字节,无索引开销 ;GUID:16字节,索引碎片率高 |
最佳实践
1. 迁移方案:从timestamp到rowversion
官方要求:新项目必须用rowversion(避免语义混淆和未来兼容风险)。
但令人疑惑的是SSMS的数据类型中并没有rowversion:为什么SSMS中缺少RowVersion数据类型
T-SQL迁移脚本:
SQL
-- 步骤 1: 检查现有 timestamp 列
SELECT COLUMN_NAME, DATA_TYPE
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'YourTable' AND DATA_TYPE = 'timestamp';
-- 步骤 2: 安全迁移(需独占锁)
ALTER TABLE YourTable
ALTER COLUMN ts ROWVERSION; -- 直接修改数据类型(SQL Server 2012+ 支持)
2. 乐观并发控制示例
正确用法(避免丢失更新):
SQL
-- 客户端读取数据
DECLARE @OldRV ROWVERSION;
SELECT Data, [timestamp] INTO @OldData, @OldRV
FROM Customer WHERE ID = 1;
-- 客户端修改后提交
UPDATE Customer
SET Data = 'NewValue'
WHERE ID = 1 AND [timestamp] = @OldRV; -- 验证版本未变
IF @@ROWCOUNT = 0
THROW 50000, 'Concurrency conflict: data modified by another user', 1;
错误用法(导致逻辑失效):
SQL
-- 致命错误:用 GETDATE() 比较 timestamp
UPDATE Customer
SET Data = 'NewValue'
WHERE ID = 1 AND [timestamp] > GETDATE(); -- 永远失败!
3. 致命错误规避:主键/时间记录禁用
为何不能用作主键:
- timestamp非唯一(高并发下可能重复),且不可预测(无法人工设置)。
- 作为主键将破坏引用完整性(外键无法关联)。
Business Central真实死锁案例:
场景:
某制造企业ERP项目,开发者误用timestamp作为时间排序键:
Bash
Customer.SetCurrentKey("SystemModifiedAt");
Customer.SetFilter("SystemModifiedAt", '>%1', CurrentDateTime);
后果:
- SQL Server执行计划强制扫描(timestamp无时间语义,索引失效)。
- 高并发下导致锁升级死锁(等待KEY锁与PAGE锁互斥)。
修复:改用SystemModifiedAt(DATETIMEOFFSET 类型):
Bash
Customer.SetCurrentKey("SystemModifiedAt");
Customer.SetFilter("SystemModifiedAt", '>%1', CurrentDateTime);