SQL Server rowversion数据类型深度解析

核心定义与用途
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];

输出结果:

SYSTEMTIMEROWVERSIONVALUEEXPLANATION
2024-06-15 14:30:22.1230x00000000000007D00x00000000000007D0 is 2000 in decimal, NOT a time value!

解读:

  • 0x00000000000007D0的十六进制值转换为十进制是2000(0x7D0=2000),表示该行是数据库的第2000次修改事件。
  • CURRENT_TIMES返回精确到微秒的系统时间,而rowversion仅为计数器。若用rowversion记录事件查询将完全失效。

与TimeStamp的关系
1. 弃用原因与等同性
特性说明
弃用时间SQL Server 2008 起标记为 deprecatedSQL 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;

输出结果:

IDTIMESTAMPDECIMALVALUENOTE
10x00000000000007D02000Value jumps by 1000 despite 1ms delay
20x0000000000000BB83000Value 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 UNIQUEIDENTIFIERrowversion: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);
订阅评论
提醒
0 评论
最旧
最新 最多投票
内联反馈
查看所有评论
滚动至顶部